#BabelOfCode 2024
Week 6
Language: Nameless experimental LISP

Confidence level: High

PREV WEEK: https://mastodon.social/@mcc/113975448813565537
NEXT WEEK: https://mastodon.social/@mcc/114433465965880352
RULES: https://mastodon.social/@mcc/113676228091546556

Okay… here things get weird!

My project to, gradually over the course of 2025, do each puzzle from Advent of Code 2024 in a different programming language I've never used before, has stalled out for exactly two months now as I've instead been creating…

…the programming language I'm going to use this week!

For "Week 5" I did TCL, and it kind of set my brain on fire with "wait… you can just DO that?".

In TCL, strings are anonymous functions and vice versa. In my old Emily language, "if" and "while" blocks were implemented by passing in lambdas; but in TCL you do the same thing by *passing in a string containing {the code to execute}*. I started trying to imagine what "hygienic" TCL would be; if TCL is a C macro, I want an ML or Rust macro. I tried to imagine something closer to LISP than TCL.

This is what I came up with; I think of it as "0-lisp". In LISP a "2-LISP" is a LISP where functions and variables live in different namespaces, and in a "1-LISP" they live in one namespace. Projecting backward, in my 0-LISP, the distinction itself disappears totally; functions are lists and *any list can be executed*. I wanted to know what this changed. Here's what I found:

It helps very little, and makes certain important things a huge pain. Oops! Still, it was very educational to learn this.

Anyway, now I have a LISP interpreter written in Rust.

https://github.com/mcclure/lisp0-experiment/tree/2025-04-07

It's a very minimal "MVP"; the language currently lacks:

- Variable scopes
- Loops
- Conditionals
- Floating point numbers
- Errors lack backtraces or line numbers (and cannot be recovered from)

It has

- One global scope
- Ints, strings, arrays and hashtables
- Tail recursion
- File I/O

But math and recursion means it's Turing complete. Which means it's sufficient to use it for a AOC problem!

GitHub - mcclure/lisp0-experiment at 2025-04-07

???? don't look at this. Contribute to mcclure/lisp0-experiment development by creating an account on GitHub.

GitHub

Now, since *I'm* the one writing this LISP, I do get to make some changes. The main thing I wanted to change, as it's the #1 thing that infuriates me writing LISP, is parentheses. This "LISP" has a syntax where each line (as separated by newlines or commas) implicitly has () around it. Meanwhile, {} and [] are shorthand, for

{x,y,z} => '((x)(y)(z))

[x,y,z] => (make-array [(x),(y),(z)])

In other words {} is functions (quoted lists) and [] is array literals.

Is this still a LISP? I don't care!

Before I can start, I need to make some utilities. By "utilities" I mean "if" and "while" statements, which again, this language currently lacks. MVP, remember. Implementing these in userland is possible! It's *ugly*, though. For `if`, I simulate branching by putting the two possible outcomes (functions) into an array, casting the condition to a bool and then the bool to an int, and using that as an index to the array.

"while" is even more nightmarish. I don't… understand the use of make-quote.

Like, I wrote the code, but I don't understand it. I initially wrote it with all the `make-quote`s being `return`s, and that didn't work, so I experimentally replaced them with make-quotes and it did work. I need to reread the interpreter code to really understand why that was required.

If even the designer can't understand the model here, the model is probably too complicated for anyone ELSE to write self-modifying code either! That's a sign of a problem, or better abstractions needed.

Anyway, I like testing things before I use them, so after implementing my "if" and "while" I decide to write a simple Fizzbuzz program. This causes me to immediately realize—

I FORGOT TO IMPLEMENT > AND <

I just forgot!! Fortunately I *did* include bit arithmetic ops, so I implement <, >, <=, >= in userland as well, by testing bit (1<<63) as a proxy for 2's compliment negative. This is actually a little worrisome. I'm not sure if this code truly "works" or if I'm just leveraging UB in Rust.

So after a prelude containing reimplementations of if, while, int comparisons, and "noop", I am now ready to implement "fizzbuzz". I do not need an implementation of "fizzbuzz", I'm supposed to be doing AOC2024 day 6. But at least I know my helper routines work!

Fizzbuzz source:

https://github.com/mcclure/aoc2024/blob/6d12e9cf65158b5f511da8545d263f4ed1dcad99/06-01-guard/src/test-fizzbuzz.l0

I was GOING to implement a `proc` that modified function code in-place to add local variable support, but debugging `while` took a lot out of me, so I think I will not do that for this project.

aoc2024/06-01-guard/src/test-fizzbuzz.l0 at 6d12e9cf65158b5f511da8545d263f4ed1dcad99 · mcclure/aoc2024

Advent of Code 2024 challenge (laid-back/"babel" version) - mcclure/aoc2024

GitHub

Got part 1 of the puzzle working this morning!

https://github.com/mcclure/aoc2024/blob/2656c75da28669ad11568fae6f5f3a9c24a311a6/06-01-guard/src/puzzle.l0

App code "proper" starts around line 124 (43-123 are "library code" I didn't post above; I implemented "for" and "switch", and a very small vector math library.)

Writing this program was not at all hard (other than the library code… `for` was painful), but nor was it particularly *pleasant*; it didn't feel fluid. That might speak poorly for my minimal LISP, and the question of whether I use it again after this project.

aoc2024/06-01-guard/src/puzzle.l0 at 2656c75da28669ad11568fae6f5f3a9c24a311a6 · mcclure/aoc2024

Advent of Code 2024 challenge (laid-back/"babel" version) - mcclure/aoc2024

GitHub
In particular, what worries me is the "hard part" of this code turned out to be the higher order functions (if, while, for, foreach, proc). This LISP is contrived compared to other LISPs, with no special "forms", and no sugar other than the {} and [] which are supposed to be swiss army knives you can construct anything else you need from. I did it this way to make self-modifying / generated-at-runtime code easy & fluid. *But that's exactly what turned out to be hardest*. And *that's* a bad sign!
@mcc does "no special forms" imply it has lazy evaluation?
@mcc (I once wrote a lisp that was optionally lazy and another that had optional continuations which would make it switch to a completely different evaluator depending on context)
@jcoglan That second thing sounds like something I'm considering, but rather than compiler modes I was going to implement it as different types of "eval". The goal here was to try to explore the TCL idea of "no anonymous functions, just eval" in a syntactically hygienic context. I imagined a base-level symbolic eval, and then a "JIT me" eval that is written in the base language and compiles a restricted, typed subset into target platform ASM at runtime…