Pretty much any language these days has to have some kind of interactive mode for playing around and testing things out. So I have been considering for a while how to make an interactive interpreter for Brat.
Now, Neko comes with a little interactive console itself (
nekoc -console), but of course that is for Neko, not for Brat. Frustratingly, Neko has a lot fewer ways to inspect itself and do dynamic stuff like evals than you might think. For example, there is no way (that I know of) to grab a binding from somewhere and use it.
Since Neko (and therefore Brat) is compiled to bytecode, I wasn’t sure there would be a way to get this all to work. On top of that, Neko modules are each their own little world, with only “exported” values being available outside.
Long story short, I was getting really discouraged while considering how I could possibly accomplish this. Trying to understand even how the Neko console worked was annoying, because most of the Neko stuff is written in C or NekoML.
But I figured out a way!
However, let me whin- I mean, explain one other frustration. When I translate Brat to Neko, I use a ton of local variables. I do this because I figure they are more likely to be garbage collected and less likely to mysteriously screw things up. But the way the Neko console maintains “state” is by copying over global variables between evaluations. So I wasn’t even thinking about that as a viable solution.
So what did I do? (Seriously, get to the point, right?)
First of all, I needed a way to input Brat code without saving it to a file, and then a way to emit Neko code without saving that to a file. This I accomplished the first by reading in Brat code from standard input. From there, it is easy enough to interpret it and do whatever with it.
Then, I needed a way to interpret the code. This I accomplished by mangling the Neko console code to fit my needs. I was able to create a new executable which reads in Neko code from the standard input and evaluates it.
But, wait. What about those local variables? That was easy enough. Simple replacement was sufficient, and, as a bonus, variables that can remain local without trouble are not touched.
When the interpreter first starts up, it sets the same things as are at the top of a regular Brat program: importing and setting core functions and objects. However, instead of executing the code within a method on base_object (like normal), I simply set this to be base_object and everything works.
The two programs (the brat script and the neko_console) communicate via pipes. It all seems to work fairly well.
I even have a very simplistic “unfinished line” detector so things like functions can span multiple lines.