The Grail System is Smalltalk, but where it got the trillion dollars of 1990-2020 revenue instead of Microsoft. Or the Grail Systems is Symbolics Genera, 2020 edition. Or it's something else. It's hard to tell.
The Igneous Linearizer is currently fantasy software.
If I wrote it right now the below would be its README.
Folder Full of Wikilinks-Markdown (FFWM):
Wikilinks are double bracket links like
[[this]]
, popularized by
🌐Roam Research. With this knowledge the rest of FFWM is self-describing.
💾Obsidian
is a popular program built on this data format.
Igneous Linearizer is a transpiler from FFWM to a single text file.
The intended use case is to:
a) write programs inside 💾Obsidian or other similar systems using granular definitions and explicit links
b) turn them into a text file
c) run them with a normal interpreter/compiler
The advantage of this is that code can be mixed with other writing and documentation in Obsidian while still knowing it's correct. This includes linking to definitions, having names auto-update when changed, transcluding definitions, etc.
The result is like literate programming, but with a graph structure instead of a tree of modules. Have a type declaration that's essential to more than one component of the code? No problem, transclude it into both.
The disadvantage is that you lose your IDE features. Thus it's only intended for snippets, examples, and other small programs.
$ ./igneous-linearizer /home/me/my-obsidian-vault/hello-world.md > hello-world.scm
Then run it as normal:
$ guile hello-world.scm
Hello, world!
The primary function of the Linearizer to is to:
a) build the reachable set of the starting page
b) serialize the starting page plus that set, topologically sorted and double newline separated, into the output file.
For example, the contents of the
hello-world.md
page from above might be:
(display [[hello-world-string]])
Along with a sibling page called
hello-world-string.md
with the contents:
(define hello-world-string
"Hello-world!")
This would output:
(define hello-world-string
"Hello-world!")
(display hello-world-string)
If a transclusion link is used via
![[foo]]
syntax, the contents of that link will be inserted into the
linking file instead of the name.
For example, to get the following output:
(define-record-type <person>
(make-person name age)
person?
(name person-name)
(age person-age))
You could use these markdown files:
(name person-name)
(age person-age)
(define-record-type <person>
(make-person name age)
person?
![[name]]
![[age]])
This has the advantage that now the references to
name
and
age
can be explicit, meaning they show up in backlinks for those
pages, you get renaming for free, etc.
An important drawback: Obsidian at least will put every transclusion on its own line, even if there's inline content before and after it. So you're limited in how you use the above style unless you control the rendering.
Modules in many languages start with a distinguished section. For instance Haskell modules will start with something like this:
{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where
import Commonmark
import Commonmark.Pandoc
If you'd like to insert a fixed preable before the rest of the output you can do that yourself, separately from the Linearizer.
However, if you'd like use a page from your FFWM as the preamble there's a flag for this, eg:
$ ./igneous-linearizer --preamble imports.md /home/me/my-obsidian-vault/hello-world.md > hello-world.hs
The argument passed to
--preable
must be a file in the same FFWM as the starting file.
Links and transclusions will be inlined the same as above. This way you can factor out imports or pragmas and use them in multiple projects. However, no reachable set of these will be built and inserted into the middle of the document, since they're not intended to be referenced by the code itself.
The Linearizer doesn't try to generate idiomatically organized code.
The only structure traditional source code provides for organization is a tree of modules. Collapsing the FFWM digraph down to this in a nice way isn't something I wanted to deal with. So Igneous Arborizer is left unclaimed.
This is meant to be similar in spirit to the super-minimal lisp in 📜Recursive Functions of Symbolic Expressions and Their Computation by Machine.
let
instead of
label
)
(When compared to the original Lisp paper)
eval
builtin function
Midnight also guarantees tail recursion elimination, though this isn't really a "change".
lambda
,
let
(recursive)
quote
,
eval
if
car
,
cdr
,
cons
,
pair?
,
list-empty?
symbol?
symbol-eq?
,
codepoints->symbol
,
symbol->codepoints
int?
,
+
,
-
,
*
,
/
,
%
,
<
,
=
,
>
crash
,
trace
,
trace-time
A magical property of computers is the ability to "change the airplane engines mid-flight." You can start with Linux, GCC and Vim and-- given enough time-- rewrite them into whatever software you'd like.
Some people are interested in the smallest systems that provide this ability. For instance Hundred Rabbits has their 💾uxn virtual computer. These minimal systems aren't nearly as useful as Linux, but they're fun in their own way. For one thing you can experiment with them easily. For another you can understand everything about them, which a refreshing experience coming from modern software environments.
Midnight's also a minimalist self-modifying system, though with a Lisp basis instead of an assembly one. It's designed to answer the question: if we start from primitives like car, cdr, and cons, what's the smallest amount of stuff we need to add to get a self-modifying system without code golfing? Are there any special characteristics of lisp that make this easier?
A miniature, Scheme-like language (💾Midnight Lisp) and a computing environment based on it (the Midnight System).
Live: midnightsystem.com
Code: github.com/seagreen/midnight
The primitives are inspired by the original Lisp paper, 📜Recursive Functions of Symbolic Expressions and Their Computation by Machine.
lambda
,
let
,
quote
,
eval
,
if
,
car
,
cdr
,
cons
,
pair?
,
list-empty?
,
symbol?
symbol-eq?
,
codepoints->symbol
,
symbol->codepoints
,
int?
,
+
,
-
,
*
,
/
,
%
,
<
,
=
,
>
See more: 💾Midnight Lisp.
Midnight is a nice base language, but we want a higher-level language to write the rest of the system in.
In a traditional system we'd write a compiler and have separate source code files for both languages.
In Midnight, taking advantage of
quote
, we write a macro system. Using it we incrementally build
the higher level langauge. This all happens in a single
expression:
(let
((midnight-plus-macros
'(
; definitions here must start with `define` or `define-macro`
))
(macroexpand ...)) ; implement the macro system
(eval (macroexpand (midnight-plus-macros))))
Using
define-macro
we write the following:
and
and
or
cond
case
quasiquote
define-struct
List functions, association list based dictionaries, etc.
These aren't part of the source code, but rather properties of the system itself.
Input: keypresses.
Output: a 80x30 fixed text display and a cursor.
Store ("hard drive"): an
S-expression. It's passed into each step of the program
along with a keypress and an
Ephem
. If a running program had a type signature it would be:
step :: Store -> Ephem -> Keypress -> (Store, Ephem)
The store must be an association list with a
display
key. The system reads off the display key after each input
to know what to display.
Ephem ("RAM"-- except tree-shaped
so TM?): A Midnight expression. Unlike the
Store
this can contain closures.
Back to the source code.
The two simplest programs I could think of to give self-modifying behavior are a text editor and a REPL. I'm still thinking about which is the best choice for this project, but it's a text editor for now.
To get self-modifying behavior the source code stores the
actual action to be taken on each keypress as a closure in
the
Ephem
. The source feeds each input into the
Ephem
closure instead of processing it itself. Thus the system can
change its own behavior by replacing the closure stored
there.
Currently the user requests such a reload with a
ctrl-ENTER
keypress. This triggers a parse of the editor's current
contents, using a parser for Midnight written in Midnight.
This is then
eval
'd. The result replaces the old
Ephem
closure.
How'd we do? Our original question was if we start from Lisp primitives like cons, car, and cdr, what's the smallest amount of stuff we need to add to get a self-modifying system without code golfing? Are there any special characteristics of lisp that make this easier?
The answer to the first part is about ~3,000 lines of code when done this way.
And does lisp make this easier? Definitely. Somewhat.
parse
easy to write.
parse
is needed to convert the text editor contents from a
string into a Midnight S-expression, so that it can be
passed to
eval
.
quote
means that it's easy to extend our base language with
a macro system. This makes writing
parse
and the editor significantly more pleasant.
quote
based alternative to this strategy: we could have
implemented a second language using plain Midnight. So
what
quote
actually gives us is this: given that at least some
code has to be in plain Midnight, we gain the option
for it to be a macro system instead of a parser for a
second language.
Midnight supports system images (Wikipedia link).
Steps:
ctrl-ENTER
The system will take up from exactly where it left off.
The difficulty in starting from a pasted system is
rebuilding the
Ephem
, since it can contain closures and thus can't be
guaranteed to be serializable.
To get around this applications are required to keep another
special key on the
Store
in addition to
display
called
main-sexp
.
main-sexp
must contain an S-expression that, when evaluated, produces
a function that can rebuild the appropriate
Ephem
.
a universal document converter