1. Converting C to Euphoria
- Posted by axtens_bruce 2 months ago
- 498 views
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
2. Re: Converting C to Euphoria
- Posted by petelomax 2 months ago
- 479 views
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.
3. Re: Converting C to Euphoria
- Posted by ghaberek (admin) 2 months ago
- 469 views
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.
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]]
4. Re: Converting C to Euphoria
- Posted by axtens_bruce 2 months ago
- 443 views
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.
5. Re: Converting C to Euphoria
- Posted by axtens_bruce 2 months ago
- 439 views
You're better off translating the purpose of the program.
Fair enough. It's not huge.
6. Re: Converting C to Euphoria
- Posted by ghaberek (admin) 2 months ago
- 410 views
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
8. Re: Converting C to Euphoria
- Posted by petelomax 2 months ago
- 391 views
I have to say, I'm quite impressed with that too!
9. Re: Converting C to Euphoria
- Posted by Icy_Viking 2 months ago
- 363 views
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.