Make A Lisp (step 4)
Continued from step 3. This step introduces user defined functions "fn*", the "if"-construct and "do".
It's been three, almost four, months since I completed step 3, so what the hell happened? Well, living with a 1.5 year old means that I have to sleep when he does or don't sleep at all. And during the fall his sleeping pattern have become increasingly erratic.
Then the waste water pipe in the "crawl space" under my house broke. "Crawl space" in quotes because there really isn't any space to crawl in. The house is 150 years old and generations of added and abandoned pipes and various other bits of house improvements have made the crawl space more into a wiggle space with no room to move your arms to do any real work (Insert Insightful Comment About Legacy Software Here). A plumber came and had a short peak into the one of the openings. He came out, said "No, thanks" and left. A relining firm came out and asked for blueprints of the plumbing system (lol!), then quoted a price close to 30% of what the we bought the whole house for with the added caveat that the relining probably wouldn't work with our pipes anyway.
In the end I went and bought a demolition hammer and made a hole in the wall of an attached cellar. From there I could remove enough rocks, dirt and poop to make the broken pipes accessible. The old pipes had the structural integrity of cookie dough, so yeah, the relining guy was propably right. It was pretty easy to replace the old pipes, but no programming done that month.
Back to MAL: The "if" (if-else really) and the "do" constructs are special forms and the whole implementation goes in in the evaluator. The "if" evaluates to it's second argument if it's first argument is true, otherwise it evaluates to the third argument if there is one. If no third argument is given it evaluates to nil.
Do simply evaluates all it's arguments in order, and returns the result of the last one.
"fn" creates a user defined function (lambda). When an user defined function is evaluated a new environment frame is created and the arguments to the function is bound in the env. Then the code portion of the fn is evaluated.
To get the tests to pass a whole bunch of functions is to be defined in the core environment. I ended up with 3 diffrent function types. I already had "native" functions, basically wrappers around native PHP functions and operators. Apart for the arithmetic operators already defined, I went ahead and added all PHP functions from the base name space. This was useful as I then could use native functions when implementing the core lambda functions. The lambda function types is already described, but that too got it's own type. The main difference between the handling is that the "native" function types needs to unwrap it's arguments from the MAL types I created as wrappers around native types. And of course, then return a wrapped type.
The third function type is called a MALFunction in my implementation. I needed it to handle the prn functions that prints its arguments as-is. Hence it cant first unwrap it's arguments. I'm not all that happy about this but here we are.
The biggest hurdle was the various print functions required for the tests to pass. I lost a bit of momentum as I can't really see the point of all those print functions, but maybe they are useful later.