06:49 pm, 8 Apr 06
cute hack: c repl
I mentioned my C read-eval-print loop idea to Satoru (check out his site: lots of cool stuff) and he said something like, "It's clear we need to do this." He then sent me a short Ruby program that did part of it, using a bunch of techniques that didn't occur to me, and after a few back-and-forth discussions I'm now up to this:
Unlike the other C-interpretation systems mentioned when I posted about this before, this works with unmodified C libraries, runtimes, etc. and doesn't require source code.
Satoru was full of good ideas for how to make this work: for example, to run a line I had thought of writing my own parser and only allowing function calls of a specific form. He suggested building a shared object for each line and bringing those in at runtime. (This sounds slow but it's actually not even noticable; consider that your shell fork+execs something every time you type in a command.) So now we can use pretty much all of C (see the example line above with the pointers and the cast), which is much more useful.
Here are some random ideas for the future work:
% ./repl
> .d float x
> x = sin(3)
> .p x
p x
$1 = 0.141120002
> *((char*)0) = 0
segfault detected; resuming.
> .p x
p x
$1 = 0.141120002
> printf("my pid is %d\n", getpid())
my pid is 11934
> .help
d "d type variable": define a new variable.
g "g foobar": run an arbitrary command through gdb.
help show help on commands.
p "p variable": print out the current value of the variable.
s cause a segfault to let crepl attempt to recover.
t test if the repl is ok by running a printf through it.Unlike the other C-interpretation systems mentioned when I posted about this before, this works with unmodified C libraries, runtimes, etc. and doesn't require source code.
Satoru was full of good ideas for how to make this work: for example, to run a line I had thought of writing my own parser and only allowing function calls of a specific form. He suggested building a shared object for each line and bringing those in at runtime. (This sounds slow but it's actually not even noticable; consider that your shell fork+execs something every time you type in a command.) So now we can use pretty much all of C (see the example line above with the pointers and the cast), which is much more useful.
Here are some random ideas for the future work:
.include "foo.h"to bring in headers,.library "foo.so"to bring in a library,.pkg "gtk+-2.0"to bring in a pkg-config package.- Allow the driver program to hook in to a glib mainloop, so that bringing in gtk would be useful. (This actually won't be too hard.)
- Make the segfault-handling more robust. Right now it replays the initial part of the program, instead of using my original forking idea. Satoru pointed out that maybe the current behavior makes more sense, though, as it'll rewind all open files, etc. to their state before the crashing code.
- Figure out a better way to handle declarations; right now, you have to mark declarations with that
.dbit, but I'd prefer to allow natural-style C declarations (int x = 3and function declarations). This may require some parsing, though.
I'm sure you're aware of this, but there's a program called
cdeclwhich parses C declarations and prints the type in a human-readable form; you might be able to nick the grammar, if not the parser itself from it. In addition to that, I've had reasonably good experiences with the Racc parser generator for Ruby (I've used it as part of the compiler that I wrote for my undergraduate thesis), and the fact that you have a native Japanese speaker working on this with you would be an immense help in that (the English documentation is not very well translated or clear).There's that whole safe function problem to worry about (linux's signal(2) has the list of safe functions).
You can browse the code at
http://neugierig.org/software/darcs/rep
Dead sexy. I particularly like the shared object per line idea, it’s one of those things where you go d’uh! as soon as you hear about it.