It was an interesting exercise for two reasons.
I did this not because I'm more comfortable in PHP than I am in JS (I'm at least equally comfortable), but because I wanted to make it difficult for me to get lazy and copy and paste parts of the JS implementation.
This turned out to be a great move because it forced me to flex PHP muscles that I haven't ever really used. For instance, I've used anonymous functions before, but the fact that PHP closures don't automatically capture variables from its parent scope meant I needed to come up with a couple of creative solutions for something that just comes along "for free" in JS.
Secondly, I implemented the entire thing using TDD.
This was, by far, the most difficult part of the exercise. Haverbeke's code is clean and succinct and after reading it I had a clear idea of what I wanted to implement. However, I forced myself to stick to the Red-Green-Refactor cycle and built the system in the smallest (reasonable) increments in functionality I could think of.
This was frustrating in parts. As this was the first time I've used TDD properly to drive the design of something bigger than a code kata or coderetreat exercise. I've used unit testing pretty extensively in some large projects, but never to explicitly drive the design, this is new for me.
Having seen Haverbeke's implementation I knew exactly where I wanted to go, but forcing myself to drive the design and implementation using TDD (and being as methodical as I was able to be) meant that I couldn't skip over a bunch of steps and simply write a PHP version of Haverbeke's code. I had to go the long way around, and I'm glad I did as it meant I learnt a lot about both the testing framework I was using (PHPSpec) as well as testing in general.
The upshot of sticking to TDD all the way through, though, was that once I'd finished the project and actually looked at the resulting code, I was pleasantly surprised to see just how clean it was. Far cleaner, I'd wager, than if I'd just bombed into writing an implementation straight. I'm also pretty sure that approaching it the way I did meant that troubleshooting was a breeze. I can imagine that if I hadn't written my tests upfront (and in the way I did) I'd have spent a bunch of time tracking down logic errors.
I'm not going to be putting the code up anywhere - the project wasn't anything more than a project a Saturday and Sunday night after the rest of the family had gone to bed. I would recommend that anyone who is interested in programming languages writes an interpreter at some stage, though.
Writing an interpreter does two things.
Firstly, it demystifies programming languages. It shows us how an interpreter is a program just like any other.
Secondly, even though an interpreter (or compiler, or assembler, or whatever you're writing) is just a program, you'll be blown away by the power of the ideas embodied in that program.
As a final note, it's been a good few years since I experienced that unique thrill that comes from having a program you've written do something cool. It was most definitely one of those moments when my interpreter could take this
and do thisdo(define(pow, fun(base, exp,if(==(exp, 0),1,*(base, pow(base, -(exp, 1)))))),print(pow(2, 10)))