1 Introduction
I wanted Worst to be a language that was easy to implement, yet flexible enough to grow beyond just being an experiment. Following a rigorous process of repeatedly deleting everything and starting over until new features stopped requiring complete redesigns, I discovered the following combination of properties that worked well together:
Stack-oriented. With a stack instead of environments of local and global variables, keeping track of data becomes simple list manipulation.
Concatenative. Programs and functions compose just by being next to each other (yes, this is just Forth so far, but bear with me). This reduces the core to a basic loop: read a token, evaluate it, repeat.
Homogeneous. It’s a lot simpler to manipulate functions when it’s the same as manipulating lists. It also means that reading code is identical to reading data.
quote and uplevel. Together, these are core to Worst’s identity. quote is the ability for any function to read the next token in the program (useful for making macros), and uplevel (borrowed from Tcl) is the ability to execute code as if it were in the calling stack frame.
Lazy parsing. It should be possible to modify the parser mid-program, so you can do things like importing syntax forms from a library. To this end, source code is parsed just before execution.
Each of these properties work together to support the others. Combined with a minimum of internal data structures (just two: the call stack and the data stack), they lead to other properties like dynamic scope and extensible error handling.
Get the next thing from the program. If it’s not a symbol, put it on top of the stack and repeat.
If it is a symbol, look it up in the definition set. (If it’s not there, check the calling stack frames too.)
If the definition is a normal function, call it and start again from the top.
Otherwise, it’s a list, so treat it as a sub-program. Step into a new stack frame and interpret it.
Repeat until the program is empty. If there’s a calling stack frame, carry on with that.
That’s it, ignoring uplevel, and the read-eval loop that does the actual syntax parsing. Most of the rest of the code is dedicated to defining built-in functions.