I like programming languages, and since I attended University many years ago, I’m attracted to their design and implementation. For example, I talked here about Micro, which I think is my latest complete release on the topic.
But implementation of programming languages is complicated, and takes a long time. That’s OK, however I think I always make the same mistakes.
To start with, I tend to implement a language that is too big. I try to do everything “the right way(tm)”, which takes even longer, and in the case of compilers, when I get to the parts that I don’t have experience and I really should investigate more, I’m overwhelmed and out of energy.
Like, why did I start working on a compiler written on Haskell when I’m still learning Haskell. Not a great idea!
So last week I was busy and tired, and frustrated, so one night I started a new project to see what I could do in a few hours, with the following conditions:
- It doesn’t have to be nice, or well done. For example: error reporting? where we are going we don’t need error reporting!
- Build an interpreter first, we’ll see if it is worth adding code generation (compiler) later.
- Use tools that I know well already.
- Keep everything small, so it is easy to change direction without a lot of refactoring.
And that’s how Funco came to be. It is very small, written in Python (3.10 or later because I used match
), it is only an interpreter taking from Python everything I could, and the user interface is very rough (raising exceptions on errors!). But it works, and it was very satisfying to write, even if it is not very useful other than helping me to solidify what I already knew.
It is inspired by lispy and Atto, and it looks like this:
# example of a factorial function
def fact(n acc)
if =(n 1)
acc
else
# tagging a tail call with "@" so it can be optimized
@fact(-(n 1) *(acc n))
end
end
# main is the entry point of a program
def main()
display("Running fact(50)...")
display(fact(50. 1))
end
# output:
# Running fact(50)...
# 3.0414093201713376e+64
It is functional, with no variables, and well… almost everything is a function –I excluded function definition and conditionals to make it easier to use–. It feels very Lisp, and the syntax is a bit Ruby-like (which is useful so get syntax highlighting).
You won’t find anything revolutionary in the code, but that wasn’t the point. I even implemented recursive tail call optimizations, because otherwise it wouldn’t be useful at all given that the loops are implemented with recursivity. For example:
# recursive for loop
def recfor(n fn)
if >(n 0)
fn(n)
@recfor(-(n 1) fn)
end
end
def main()
recfor(10000 display)
end
Because there is no “return”, it is required to tag the tail calls with @
so the interpreter tries to optimise that call avoiding hitting a stack limit (and improving performance, although speed was never in my plans).
Perhaps I will put some more time to add nicer error reporting, just in case I can use this funco as a base for future experiments. Now that I have something small and easy to modify, it shouldn’t been that costly to make small experiments with code generation!
Edit (2024-04-29), I have added more examples. It is a toy language, but there is some brain teaser about writing these that I like.