ci - a tiny C interpreter

Richard Suchenwirth 2007-10-09 - Playing with tcltcc and its Critcl emulation, I had the idea of writing a very little C "compiler" which takes a (moderately) well-formed C source file, compiles (to memory) and executes it (by calling its main() function), so more along the lines of a C interpreter.

One problem I encountered is that char** seems not to be supported yet, so for first simplification I made argv a single string. Here's my script (ridiculously simple, indeed):

 #!/usr/bin/env tclsh
 #-- ci.tcl -- a tiny C interpreter built on tcltcc

 package require critcl
 set f [open [lindex $argv 0]]
 set data [read $f]
 close $f

 critcl::ccode $data
 critcl::cproc main {char* argv} int {main(1,argv);}
 main [lindex $argv 1]

and a tiny C program source to test it with (ci_test.c):

 /* demo program for ci.tcl */
 #include <stdio.h>

 int main(int argc, char* argv) {
        printf("hello, %s\n", argv);
        return 0;

which, when run, provides yet another solution for the classic K+R "hello, world" problem:

 /Tcl/lib/tcc02> ci.tcl ci_test.c world
 hello, world

Brian Theado 2007-10-09 - Nice. I saw in the tcc README file you can use the tcc executable in the shebang line on unix to get C-based shell scripting (i.e. '#!/usr/bin/tcc -run'). You could do something similar with your ci.tcl (I guess you would need to parse out the shebang line like 'tcc -run' must do).

I wonder if there are any ideas to borrow from C REPL (Read-Eval-Print-Loop) at [1 ]

RS: Well, tcc is pretty incremental, so we can just use the read-eval-print loop of an interactive tclsh for that, witness this sample session:

 /Tcl/lib/tcc02> tclsh
 % package req critcl
 % critcl::cproc mul {double a double b} double {return a*b;}
 % mul 6 7

Or this:

 % critcl::ccode {#include <string.h>}
 % critcl::cproc strerror {int n} char* {return strerror(n);}
 % strerror 1
 Operation not permitted
 % strerror 2
 No such file or directory
 % strerror 3
 No such process

RS 2007-10-10: The breaking of the main() interface above left me restless, so I hacked up this variation, which in all ugliness (global, static, strtok..) at least accepts a canonical main():

 #!/usr/bin/env tclsh
 #-- ci.tcl -- a tiny C interpreter built on tcltcc
 package require critcl
 set f [open [lindex $argv 0]]
 set data [read $f][close $f]
 critcl::ccode $data
 critcl::ccode {
    #include <string.h>
    #define MAX_ARGC 64
    int g_argc = 0;
    static char** parse_args(char* args) {
        static char* s_argv[MAX_ARGC+1];
        char *cp = strtok(args," ");
        while(cp) {
            s_argv[g_argc] = cp;
            if(++g_argc >= MAX_ARGC) break;
            cp = strtok(NULL, " ");
        s_argv[g_argc] = NULL;
        return &s_argv;
 critcl::cproc main {char* args} int {
    char** argv = parse_args(args);
    return main(g_argc, argv);
 main $argv

The sample C program is now more canonical and robust, and supplies a default argument ("you") if there is none in argv:

 /* ci_test.c -- demo program for ci.tcl */
 #include <stdio.h>

 int main(int argc, char* argv[]) {
    char* cp = "you";
    if(argc>1) cp = argv[1];
    printf("hello, %s\n", cp);
    return 0;


 /Tcl/lib/tcc02> ci.tcl ci_test.c world
 hello, world
 /Tcl/lib/tcc02> ci.tcl ci_test.c
 hello, you

RFox 3/13/2012 - In case you are looking for a full fledged C interpreter: