Writing a Lisp parser is easy. Walking Lisp code is easy. Serializing Lisp code is easy. Adding a new primitive is easy. Adding very basic syntax transforming macros is easy. All of these are virtually trivial if your host language is a Lisp, as was the case with Co2.
What they didn’t do is what many people might think are table stakes with Lisp: writing a garbage collector, writing a runtime, supporting lambdas, and so on. Those are unreasonable asks for 2K RAM on a 6502. I wouldn’t say they wrote a bonafide Lisp, but they made use of many ideas of Lisp successfully to write a game that is very surprisingly readable while not being too abstract over assembly.
Since Lisp began in the 1950s, it has always needed to stay tied to the low level. Even today, with SBCL or CCL, you can write your own assembly code. One relevant thing to the article is Baker’s COMFY 6502 language for writing assembly code [0]. A few implementations can be found on GitHub.
[0] http://home.pipeline.com/~hbaker1/sigplannotices/sigcol04.pd...
* The simple syntax of stripped-down Lisp is very amenable to application-specific or domain-specific macros. This turns out to be a convenient way to do things that often the language or compiler alone can't do as well, if you used only functions, data, and conventions.
* That the syntax is so simple, and can also be first-class data that is displayed from the programming environment like it looks in syntax, makes it especially nice for things like intermediate representations that are refined incrementally. For example, you can show a translation series of steps that go from syntax parse, to resolutions, to phases of optimizations, to high-level assembler, to a very low-level target code (still represented with parentheses and "opcodes"), from which you write bytes. This can also be convenient.
In this particular case, they're using Racket (an implementation of a dialect of Scheme) to implement a compiler for a Lisp dialect they invented. Using Racket gives them both a nice general-purpose language for implementing their compiler, and happens to already have a lot of tools for parsing their own stripped-down Lisp and manipulating it.
IIUC, Naughty Dog used Racket for a similar purpose: to implement their own Lisp. For a narrative DSL for some AAA titles.
Forth implementations on the 6502 uses a stack and require more RAM than Co2 which uses a compiled stack.
I feel like Co2 would compile to faster code but of course I haven't benchmarked this. The reason I feel this is so is that I way I understand compiled forth is that the 'words' are still threaded so you don't escape the interpreter overhead.
Soft reasons.
Co2 takes away the chore of parsing, in Forth you are the parser. It's simpler but more error prone and harder to read (subjectively).
Forth is postfix notation which for a lot of people is challenging.
Parsing Lisp in Lisp is so easy because it’s free.
I've opted for a less elegant, but technically simple strategy: I'm writing all the build tools/content tools in Clojurescript, and then writing only the core game engine in raw C#.
Next, I'm using http://bridge.net to cross-compile the game engine into javascript, which I can then link to from clojurescript so that all unit tests and 90% of all QA testing can be done via clojurescript tooling, without any C# in sight.
This allows me to deploy a commercial game in "native" C# without any performance penalty, but with as few lines of C# as humanly possible.
Can you fire up a REPL while the game is running? That would be huge.
Many years ago I experimented with the idea of creating games for iOS in Chicken Scheme. Because I'm exceedingly lazy and did not want to bother with cross-compilation – unless it became a serious project – I just told Chicken's compiler to stop at the C code generation step. The Makefile would then copy the generated (and quite unreadable) C code to the XCode project, and then compile the whole thing together with any Objective-C code I had.
With very little setup code, you could embed arbitrarily large Chicken programs. And given Chicken's excellent C interop, the Objective-C code could easily call Scheme functions (the reverse is not as trivial, so I just wrote wrappers for the handful of Objective-C functions I needed – which weren't many in a game).
The only piece missing at the time was that Chicken did not have OpenGL-ES bindings. I solved that by copying code from Gambit Scheme, and using a couple of very trivial macros to make it compatible.
That worked beautifully. I could even start a remote REPL and instantly change running code over the network, no matter if it was running in a real device or the simulator. And I mean instantly: the next rendered frame would already have the changes.
Then I hit my roadblock: I had successfully solved the technical problem, so I lost interest in pursuing the game, which was ostensibly the reason why I had embarked on this detour to begin with. Oh well.
On the other hand, my strategy makes the final deployed application dead simple and fast, but leads to a build process that's complex and messy.
It seems objectively true that the latter approach is going to be less risky, because if the final deployed game doesn't work well, the whole project is f###ed.
The only way to mitigate this risk and still stay with 100% Lisp is to create your own Lisp compiler, which is exactly what OP did... but that way lies insanity :)
I didn't get any pop-up "pardon[ing] the interruption" or asking me to join a mailing list, or anything! To think this used to be the normal way of doing things on the web.
For what it's worth I hate medium as well. But I would recommend ghost.org if you want to go with self hosting.
Looks reader-mode-compatible in Safari, which solves the problem for any site that's mostly content.
In the other hand the racket lisp behind the scenes _generates_ the assembly code based on some of the scheme primitives rather than porting its whole Racket runtime there, which in spite of not being the same as running a lisp in the NES hardware is still impressive.
With developer mindshare flocking to the Haskell view of FP, for the Lisp family, only the Clojure users seem to still hold purity in high regard.
You can still do this kind of work in Haskell as well. It is a great imperative language.
Though as someone who has written assembler compilers in Common Lisp... it's felt relatively easy to do in CL. I've only heard people talk about writing control software for drones from Haskell. I have no idea how one would do that in practice though.
With the former favouring OOP and looping over recursion
Not my experience at all, fwiw.In extremely latency sensitive applications, the mix of stack and instruction cacheline faults can cause significant overhead.
It makes all local variables have a fixed place in RAM. This obviously removes support for recursion. A naive implementation would cause this to explode memory usage.
However, you can analyze your entire program and any variables (including across function boundaries) that are never live at the same time are now allowed to share storage. Now you never push or pop variables to your stack, and your dynamic stack size is only the maximum call depth (no frame pointer needed because you don't have any on-stack variables, so you only save the return pointer to the stack).
(I'm not related to the project, but it's one of the few games I've 100% completed because it was just so good)
Does anyone know if there are any MUDs out there coded in lisp? I didn't find any good results in google aside from the MPI lisp-like language built on top of FurryMUCK.
You can find some information there:
https://archive.bleu255.com/nakedonpluto/about/
https://gitorious.org/naked-on-pluto/game-server (cert expired)
https://gitorious.org/naked-on-pluto/game-client (cert expired)
There's a podcast for present day NES developers called The Assembly Line.[1] I'm sure they'd enjoy this story and talking to you.