1. Converting C to Euphoria

Are there any tools for converting C to Euphoria? Or at least some discussion.

I was thinking of implementing the TRAC programming language, converting the nntrac at https://git.sr.ht/~luxferre/nntrac to Euphoria and compiling it to EXE ultimately for setting up as a Exercism language.

-Bruce

new topic     » topic index » view message » categorize

2. Re: Converting C to Euphoria

Unlikely*. The closest thing I've got is p2js, which translates Phix to JavaScript**, and (at least theoretically) vice-versa, and (at least theoretically) that could be extended to do [some of the gruntwork of] C to phix, but it would no doubt end up rather buggy and be both quicker and easier and just better to do it as a one-off by hand, which I could if you wanted to pay for it! Any specific reason why you don't want to just use the C-generated binary as-is?

* An automated tool that deals correctly with 0-based indices to 1-based would be tricky enough, mapping char* to string might be reasonably straightforward but any other C pointers would be another huge ball of mud.
** As in JavaScript that uses 1-based indices, and without any direct use of pointers.

new topic     » goto parent     » topic index » view message » categorize

3. Re: Converting C to Euphoria

axtens_bruce said...

Are there any tools for converting C to Euphoria? Or at least some discussion.

I don't recall anything specific, no. Rob often promoted the language as being able to find and solve problems when porting from C to Euphoria, though I'd imagine that was often done by hand.

Doing a perfect one-for-one translation from C isn't really worthwhile anyway, unless you need to somehow maintain some specific compatibility with C. You're better off translating the purpose of the program.

axtens_bruce said...

I was thinking of implementing the TRAC programming language, converting the nntrac at https://git.sr.ht/~luxferre/nntrac to Euphoria and compiling it to EXE ultimately for setting up as a Exercism language.

Overall it's quite possible. You could probably start with the C code as-is, considering you can build the code as a shared library, and then wrap that in Euphoria. Or use the executable version for testing against your Euphoria TRAC interpreter.

The code itself is less than 1200 lines and makes use of several global values and arrays. The primitives and forms arrays could easily be sequences and name lookup could probably be sped up with a map. The rest is mostly string parsing for the code and built-in primitives.

Here's a rough translation of the primitives registration functions. You can see how it's nearly but not entirely one-for-one. It's still very "C" but also very "Euphoria." I think the hardest part is going to be all of the strtok calls and ensuring the zero vs. one offsets are all correct.

enum PRIM_NAME, PRIM_FUNC 
sequence nnt_primitives 
 
/* find a primitive function index by name, -1 if not found */ 
public function nnt_findprimitive(sequence name) 
  for i = 1 to length(nnt_primitives) do 
    if equal(nnt_primitives[i][PRIM_NAME], name) then 
      return i /* found */ 
    end if 
  end for 
  return -1 /* nothing found */ 
end function 
 
/* register a primitive function, overwrite if already exists */ 
public procedure nnt_regprimitive(sequence name, integer func) 
  integer pindex = nnt_findprimitive(name) 
  sequence newprim = {"",-1} /* create a new primitive template */ 
  newprim[PRIM_NAME] = name /* fill the name */ 
  newprim[PRIM_FUNC] = func /* fill the handler id*/ 
  if pindex = -1 then 
    /* create a new table entry */ 
    nnt_primitives = append(nnt_primitives, newprim) 
  else 
    /* replace the entry */ 
    nnt_primitives[pindex] = newprim 
  end if 
end procedure 
 
... 
 
/* init the resources and built-in primitives */ 
public procedure nnt_init() 
  nnt_forms = {} /* init the forms table */ 
  nnt_primitives = {} /* init the primitives table */ 
--nnt_rngs = ??? /* init the PRNG */ 
  nnt_regprimitive("ps", routine_id("prim_ps")) 
  nnt_regprimitive("rc", routine_id("prim_rc")) 
  nnt_regprimitive("rs", routine_id("prim_rs")) 
-- ... 
end procedure 

Hope this helps!

-Greg

P.S. The Creole parser ate the tilde ~ in your URL. I've edited your post to correct it.

You can prevent this by escaping with another tilde or enclosing the URL in brackets, like this:

https://git.sr.ht/~~luxferre/nntrac 
-or- 
[[https://git.sr.ht/~luxferre/nntrac]] 

new topic     » goto parent     » topic index » view message » categorize

4. Re: Converting C to Euphoria

petelomax said...

Any specific reason why you don't want to just use the C-generated binary as-is?

I want to change its file handling. And while I'm not a guru Euphoria programmer, I'm better at it than I am at C.

new topic     » goto parent     » topic index » view message » categorize

5. Re: Converting C to Euphoria

ghaberek said...

You're better off translating the purpose of the program.

Fair enough. It's not huge.

new topic     » goto parent     » topic index » view message » categorize

6. Re: Converting C to Euphoria

axtens_bruce said...

I want to change its file handling. And while I'm not a guru Euphoria programmer, I'm better at it than I am at C.

Looks like you can override the primitives by simply re-registering them with your own implementation using a function callback. To test this I wrapped the shared library and rewrote the main() function from nntrac.c. I'm also using strtok() from libc (or msvcrt.dll) to easily reproduce the existing prim_ps() function. If you want to reimplement some of the other primitives, you'll need to use realloc() to resize the memory buffer provided in the res parameter of your callback, so I've added that to the wrapper as well.

Makefile (quick and dirty, bare-minimum, and mostly cross-platform)

$(if $(OS),nntrac.dll,libnntrac.so): nntrac.o; $(CC) -shared -o $@ $^ 
nntrac.o: nntrac.c; $(CC) -std=c99 -Os -s -fPIC -DNNT_EMBED -o $@ -c $< 
clean:; $(if $(OS),del /Q nntrac.dll,$(RM) libnntrac.so) nntrac.o 

nntrac.e (wrapper for shared library)

public include std/dll.e 
public include std/machine.e 
 
public enum type NNT_MARKERS /* various markers: 248 to 255 never occur in UTF-8 */ 
  NNT_AFST=-8, /* active function start */ 
  NNT_NFST,    /* neutral function start */ 
  NNT_EOF,     /* end of function */ 
  NNT_ADEL,    /* argument delimiter */ 
  NNT_SEGGAP   /* segment gap character */ 
end type 
 
public enum type NNT_MODES /* operation modes */ 
  NNT_NORMAL=1, NNT_LEGACY, NNT_SECURE 
end type 
 
public constant NNT_ADEL_S = allocate_string({NNT_ADEL}) 
 
constant nntrac = open_dll({"nntrac.dll","libnntrac.so"}), 
  _nnt_init = define_c_proc(nntrac,"nnt_init",{}), 
  _nnt_assignform = define_c_proc(nntrac,"nnt_assignform",{C_POINTER,C_POINTER,C_UINT,C_INT}), 
  _nnt_regprimitive = define_c_proc(nntrac,"nnt_regprimitive",{C_POINTER,C_POINTER}), 
  _nnt_proc = define_c_proc(nntrac,"nnt_proc",{C_POINTER,C_UINT}), 
  _nnt_finish = define_c_proc(nntrac,"nnt_finish",{}) 
 
/* init the resources and built-in primitives */ 
public procedure nnt_init() 
  c_proc(_nnt_init,{}) 
end procedure 
 
/* assign a value to a form, creating it if doesn't exist */ 
public procedure nnt_assignform(sequence name, sequence value, integer len=length(value), integer ptr=0) 
  atom pvalue = allocate_data(len,1) poke(pvalue,value) 
  c_proc(_nnt_assignform,{allocate_string(name,1),pvalue,len,ptr}) 
end procedure 
 
/* register a primitive function, overwrite if already exists */ 
public procedure nnt_regprimitive(sequence name, sequence func, integer rid=routine_id(func)) 
  c_proc(_nnt_regprimitive,{allocate_string(name,1),call_back(rid)}) 
end procedure 
 
/* main TRAC processing algorithm */ 
public procedure nnt_proc(sequence prog, integer len=length(prog)) 
  atom pprog = allocate_data(len,1) poke(pprog,prog) 
  c_proc(_nnt_proc,{pprog,len}) 
end procedure 
 
/* free the interpreter resources */ 
public procedure nnt_finish() 
  c_proc(_nnt_finish,{}) 
end procedure 
 
export constant libc = open_dll({"msvcrt.dll",""}), 
  _realloc = define_c_func(libc,"realloc",{C_POINTER,C_SIZE_T},C_POINTER), 
  _strtok = define_c_func(libc,"strtok",{C_POINTER,C_POINTER},C_POINTER) 
 
/* reallocate a block of memory */ 
public function realloc(atom ptr, atom size) 
  return c_func(_realloc,{ptr,size}) 
end function 
 
/* find the next token in a string */ 
public function strtok(atom ptr, atom delim) 
  return c_func(_strtok,{ptr,delim}) 
end function 

nntrac.ex (example program from nntrac.c)

include std/io.e 
include nntrac.e 
 
/* ps primitive: print string */ 
function prim_ps(atom arglist, atom res, atom reslen) 
  atom arg = strtok(arglist, NNT_ADEL_S) 
  /* skip the first one (the ps string) */ 
  loop do 
    arg = strtok(NULL, NNT_ADEL_S) 
    if arg then 
      /* print the raw sequence */ 
      ? peek_string(arg) 
    end if 
    until arg = NULL 
  end loop 
  return res 
end function 
 
procedure main(sequence argv=command_line(), integer argc=length(argv)) 
 
  integer fn = STDIN 
  sequence fname = "-" /* stdin by default */ 
  if argc > 2 then argc = fname[3] end if 
 
  if not (fname[1] = '-' and length(fname) = 1) then 
    fn = open(fname, "rb") 
    if fn = -1 then 
      puts(STDERR, "Error\n") 
      abort(1) 
    end if 
  end if 
 
  sequence prog = "" 
  integer proglen = 0 
 
  if fn = STDIN then /* interactive session */ 
    prog = "#(ps,#(rs))" 
    proglen = length(prog) 
    puts(STDERR, "nntrac by Luxferre, 2023, public domain\n") 
  else 
    prog = read_file(fn) 
    proglen = length(prog) 
  end if 
 
  nnt_init() /* init processing resources */ 
 
  /* register our own ps primitive (print string) */ 
  nnt_regprimitive("ps", "prim_ps") 
 
  if argc > 2 then /* populate nnt-argc and nnt-argv forms */ 
 
    sequence buf = sprintf("%d", argc - 2) 
    nnt_assignform("nnt-argc", buf) 
 
    buf = "" /* reuse the buffer for nnt-argv */ 
    sequence segsep = {NNT_SEGGAP, 1, 0} 
 
    for i = 3 to argc do 
      buf &= argv[i] /* append the parameter */ 
      buf &= segsep  /* and the segment separator */ 
    end for 
 
    nnt_assignform("nnt-argv", buf) 
 
  end if 
 
  nnt_proc(prog, proglen) /* start main processor */ 
  nnt_finish() /* finalize processing resources */ 
  close(fn) 
  puts(STDOUT, "\n") /* just print a newline before exiting */ 
 
end procedure 
 
main() 

-Greg

new topic     » goto parent     » topic index » view message » categorize

7. Re: Converting C to Euphoria

Wow, Greg, thanks!

new topic     » goto parent     » topic index » view message » categorize

8. Re: Converting C to Euphoria

I have to say, I'm quite impressed with that too!

new topic     » goto parent     » topic index » view message » categorize

9. Re: Converting C to Euphoria

ghaberek said...
axtens_bruce said...

I want to change its file handling. And while I'm not a guru Euphoria programmer, I'm better at it than I am at C.

Looks like you can override the primitives by simply re-registering them with your own implementation using a function callback. To test this I wrapped the shared library and rewrote the main() function from nntrac.c. I'm also using strtok() from libc (or msvcrt.dll) to easily reproduce the existing prim_ps() function. If you want to reimplement some of the other primitives, you'll need to use realloc() to resize the memory buffer provided in the res parameter of your callback, so I've added that to the wrapper as well.

Makefile (quick and dirty, bare-minimum, and mostly cross-platform)

$(if $(OS),nntrac.dll,libnntrac.so): nntrac.o; $(CC) -shared -o $@ $^ 
nntrac.o: nntrac.c; $(CC) -std=c99 -Os -s -fPIC -DNNT_EMBED -o $@ -c $< 
clean:; $(if $(OS),del /Q nntrac.dll,$(RM) libnntrac.so) nntrac.o 

nntrac.e (wrapper for shared library)

public include std/dll.e 
public include std/machine.e 
 
public enum type NNT_MARKERS /* various markers: 248 to 255 never occur in UTF-8 */ 
  NNT_AFST=-8, /* active function start */ 
  NNT_NFST,    /* neutral function start */ 
  NNT_EOF,     /* end of function */ 
  NNT_ADEL,    /* argument delimiter */ 
  NNT_SEGGAP   /* segment gap character */ 
end type 
 
public enum type NNT_MODES /* operation modes */ 
  NNT_NORMAL=1, NNT_LEGACY, NNT_SECURE 
end type 
 
public constant NNT_ADEL_S = allocate_string({NNT_ADEL}) 
 
constant nntrac = open_dll({"nntrac.dll","libnntrac.so"}), 
  _nnt_init = define_c_proc(nntrac,"nnt_init",{}), 
  _nnt_assignform = define_c_proc(nntrac,"nnt_assignform",{C_POINTER,C_POINTER,C_UINT,C_INT}), 
  _nnt_regprimitive = define_c_proc(nntrac,"nnt_regprimitive",{C_POINTER,C_POINTER}), 
  _nnt_proc = define_c_proc(nntrac,"nnt_proc",{C_POINTER,C_UINT}), 
  _nnt_finish = define_c_proc(nntrac,"nnt_finish",{}) 
 
/* init the resources and built-in primitives */ 
public procedure nnt_init() 
  c_proc(_nnt_init,{}) 
end procedure 
 
/* assign a value to a form, creating it if doesn't exist */ 
public procedure nnt_assignform(sequence name, sequence value, integer len=length(value), integer ptr=0) 
  atom pvalue = allocate_data(len,1) poke(pvalue,value) 
  c_proc(_nnt_assignform,{allocate_string(name,1),pvalue,len,ptr}) 
end procedure 
 
/* register a primitive function, overwrite if already exists */ 
public procedure nnt_regprimitive(sequence name, sequence func, integer rid=routine_id(func)) 
  c_proc(_nnt_regprimitive,{allocate_string(name,1),call_back(rid)}) 
end procedure 
 
/* main TRAC processing algorithm */ 
public procedure nnt_proc(sequence prog, integer len=length(prog)) 
  atom pprog = allocate_data(len,1) poke(pprog,prog) 
  c_proc(_nnt_proc,{pprog,len}) 
end procedure 
 
/* free the interpreter resources */ 
public procedure nnt_finish() 
  c_proc(_nnt_finish,{}) 
end procedure 
 
export constant libc = open_dll({"msvcrt.dll",""}), 
  _realloc = define_c_func(libc,"realloc",{C_POINTER,C_SIZE_T},C_POINTER), 
  _strtok = define_c_func(libc,"strtok",{C_POINTER,C_POINTER},C_POINTER) 
 
/* reallocate a block of memory */ 
public function realloc(atom ptr, atom size) 
  return c_func(_realloc,{ptr,size}) 
end function 
 
/* find the next token in a string */ 
public function strtok(atom ptr, atom delim) 
  return c_func(_strtok,{ptr,delim}) 
end function 

nntrac.ex (example program from nntrac.c)

include std/io.e 
include nntrac.e 
 
/* ps primitive: print string */ 
function prim_ps(atom arglist, atom res, atom reslen) 
  atom arg = strtok(arglist, NNT_ADEL_S) 
  /* skip the first one (the ps string) */ 
  loop do 
    arg = strtok(NULL, NNT_ADEL_S) 
    if arg then 
      /* print the raw sequence */ 
      ? peek_string(arg) 
    end if 
    until arg = NULL 
  end loop 
  return res 
end function 
 
procedure main(sequence argv=command_line(), integer argc=length(argv)) 
 
  integer fn = STDIN 
  sequence fname = "-" /* stdin by default */ 
  if argc > 2 then argc = fname[3] end if 
 
  if not (fname[1] = '-' and length(fname) = 1) then 
    fn = open(fname, "rb") 
    if fn = -1 then 
      puts(STDERR, "Error\n") 
      abort(1) 
    end if 
  end if 
 
  sequence prog = "" 
  integer proglen = 0 
 
  if fn = STDIN then /* interactive session */ 
    prog = "#(ps,#(rs))" 
    proglen = length(prog) 
    puts(STDERR, "nntrac by Luxferre, 2023, public domain\n") 
  else 
    prog = read_file(fn) 
    proglen = length(prog) 
  end if 
 
  nnt_init() /* init processing resources */ 
 
  /* register our own ps primitive (print string) */ 
  nnt_regprimitive("ps", "prim_ps") 
 
  if argc > 2 then /* populate nnt-argc and nnt-argv forms */ 
 
    sequence buf = sprintf("%d", argc - 2) 
    nnt_assignform("nnt-argc", buf) 
 
    buf = "" /* reuse the buffer for nnt-argv */ 
    sequence segsep = {NNT_SEGGAP, 1, 0} 
 
    for i = 3 to argc do 
      buf &= argv[i] /* append the parameter */ 
      buf &= segsep  /* and the segment separator */ 
    end for 
 
    nnt_assignform("nnt-argv", buf) 
 
  end if 
 
  nnt_proc(prog, proglen) /* start main processor */ 
  nnt_finish() /* finalize processing resources */ 
  close(fn) 
  puts(STDOUT, "\n") /* just print a newline before exiting */ 
 
end procedure 
 
main() 

-Greg

That's really cool, Greg. I didn't know you could load shared libraries like that.

new topic     » goto parent     » topic index » view message » categorize

Search



Quick Links

User menu

Not signed in.

Misc Menu