💾 Archived View for stack.tilde.cafe › gemlog › 2022-12-25.candforth.gmi captured on 2024-07-08 at 23:39:48. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-09-08)
-=-=-=-=-=-=-
I presented my project as an interactive linker for a harnessed C compiler. And it true: I've implemented the infrastructure for extracting compiled C functions and data out of ELF object files, and interning them into the system, including being able to replace old versions with new ones. I set up a basic way to track ingested code as symbols, which are kept in packages, kind of like Lisp. The whole thing lives as an image which can be saved and restored. Oh, and I made provisions for keeping the sources of everything ingested.
But in reality, harn is much more than that: it is an artifact management system -- a database of sorts, for keeping live code and messing with it. Harnessing a C compiler is a good party trick -- and provides access to the vast infrastructure of libraries -- but is not the end-all.
There is no reason not to have another way to generate code.
I rigged up a simple REPL. It is capable of recognizing a few hardwired commands; otherwise it assumes that I want to compile and execute a C expression (specifically if it ends with a semicolon). To do so, I quickly generate a file full of headers for all visible symbols, and concoct a function containing the REPL expression, compile it, ingest it, execute it and remove all traces of it in the system. It sounds complicated, but is instantaneous.
The problem is passing parameters -- and returning the result. Where do they come from, if they are not literals? Where do the results go? If we need to define temporary global variables every time we want to run a simple expression, the system is really cumbersome. And we can't use a generic 'return' variable because of the darned type system and function prototypes...
What if the environment had a short-term anonymous environment that kept a few of the last results available for further command-line calls? Well, that sounds like Forth and its datastack, doesn't it?
Turns out it is not difficult to add a Forth-like layer to the project. The register ABI is transient - all it cares about is that the correct registers are loaded prior to the call and the stack is aligned. In-between calls, we can have Forth - a datastack environment which keeps the results, and makes them available to calls.
This requires much less hassle between calls -- at the expense of losing most of the static type-checking, which I don't care too much about anyway. It's a win.
The datastack is a useful feature -- not just for the REPL. As long as it's in place, we can integrate Forth-like languages - as native compilers, or token interpreters, into the system. There are a few ways of doing it, and I am going to spend a bit of time thinking about that.