fir – Portable Compact AST for pure concatenative functions

RLP as both AST and binary for LISPs and Forths

RLP makes a good bytecode encoding because half of the one-byte values are serialized directly, and a list of these is serialized directly in a byte sequence. Naturally, you will want core ops that manipulate RLP. This gives you a nice homoiconic foundation for your deterministic VM.

A core subset of basic stack ops for specifying pure functions that are guaranteed to terminate is useful for any higher level language. You can statically determine worst-case execution cost. This core has a bind/call that is equivalent to inlining, and a frame/eval for capturing and advancing emulation frames.

All items on the stack are RLP objects, recursive lists with buffers at the leafs. RLP operations all take constant time in the implementation, this requirement basically defines the set of operations. These are usable like cons lists or buffer, depending on node type.

op table

00-
3f  push/<k>
40  push/data <d>

    stack/dup
    stack/drop
    stack/swap
    stack/rot

    rlp/isBlob
    rlp/isList

    num64/cast, cast-try
    num64/add,sub,...,not,and,or
    num64/add-try, sub-try, etc
    
    (rlp/isBlob)
    blob/len
    blob/cat
    blob/slice
    [blob/zeros <k>]

    (rlp/isList)
    list/len
    list/cat
    list/cons
    list/head
    list/tail
    list/lempty
    list/wrap
    list/unwrap

    [control/case [k,f]* f?]
    [control/loop k f]

    [control/bind f* f]
    [control/call k]
    
    control/break      // variants:  (loop|case)*cleanstack?
    control/continue   // variants:  (loop|case)*cleanstack?
    control/ret        // variants:  cleanstack?*flag?
    control/trap       // variants:  many...

70  [eval/step k]      // advance the state of the emulation frame on the data stack
71  [eval/frame k]     // put binding k onto the data stack as a fresh evaluation frame
7f  unreachable

#  extensions

80+ extended instruction set (2-byte+ operations)