Definitely downshifting to one every two days, so 15 interpreters for the month. It's a much more feasible speed. (And I'm already a day behind it! But I have a hope of catching up this time. ;-)
Stuff about Pike
This time I used
Pike, a slightly obscure language originally derived from LPC, the scripting language of the LPMud. Having been a wizard on an LPMud back in the day, I was already somewhat familiar with it.
The main thing wrong with Pike is that it has a type system not even a mother could love. It's dynamic-with-refinements, meaning that everything is dynamic (or in Pike-speak, "mixed") under the covers, but you can give types to your variables and it should check them against these refined types. Except it doesn't work. Zero is a legitimate member -- in fact the default member -- of every type. You seem to be able to get the empty list an any type too; it's not at all clear to me when you can accidentally get something at the wrong type.
No algebraic datatypes. I will never again love a language that doesn't have them. Of course, since it's got "mixed", you can pretty much do whatever you need to anyway; and if that's not good enough (as it wasn't for me in one case) you have OO-style classes. It does let you write things like "typedef int|string|list(sexp) sexp", i.e. "type sexp = int | string | sexp lisp". But they don't actually _work_. It expands the recursion about two levels and stops silently, filling in "object" at the leaves, WHICH DOESN'T EVEN MAKE SENSE SINCE "object" != "mixed" and can't even hold things of type sexp.
A lot of things work that really really shouldn't; the operational semantics seem to be roughly "try to do anything sane at all with what you're given". You can call methods or access properties that don't exist on lists; it wasn't consistent enough for me to figure out what it was actually doing, but I seemed to be hitting some internal mechanism for mapping/iterating over the list. You can say "function args" instead of "function(args)" and there's no error; it declares "args" whose type is the return type of the function. >.<
The REPL also sucks and can't do a bunch of things, like define typedefs. And the errors are unclear.
Basically the language is incredibly unpolished, and I'm not sure anybody's polishing it.
On the positive side: Automatic backtraces on error are FUCKING AMAZING. Seriously, having been working so far in languages that don't provide backtraces (or not by default, anyway): There's no excuse for not supporting this. Even if you're a non-stackful, you can still implement this -- you just don't get it for free. Built-in tracing and logging, in general, are an important and tragically rare feature.
It's also nice going back to a language with block-structure, or in Lisp terms "implicit progn". Every function body is a list of expressions/statements; as opposed to e.g. SML where it's a single expression, and if you want anything else you need to add a let, or a layer of parentheses and semicolons. (Semicolons would be fine if the precedence were slightly better...) Block structure allows for easily doing things like adding tracing statements to the beginning of functions. (It does _not_ allow for easily adding them to the ends of functions; I'd love to see function syntax with "finally" blocks allowing for this.)
Stuff about implementing Lisp
It's interesting to note that a metalanguage-level "map", even with mutation, is not sufficient by itself to create a mutable environment that can be captured in closures. We need a structure that lets a closure shadow _some_ old mappings, but still "see through" to other old mappings; thus my class Env which performs this function. I rewrote to that about halfway through implementation.
Code:
here.
Interesting bugs: Converting from environment map to environment class, forgot to change how I checked presence in the mapping (for maps there is a nasty builtin implementing "does this returned zero have the magic 'not in the hash' bit set on it, or is it a real zero?"); in eq primitive, returned metalanguage truth (zero or one) instead of object language truth (empty list or anything-else -- zero is true in lisp); in minus primitive, implemented (- x y_0 ... y_n) as "x - x - y_0 - ... - y_n" instead of "x - y_0 - ... - y_n", i.e. I mapped over too much of the arg array.
Next: Maybe Scala? (or OCaml, Go, Forth...)