Builtins
The operations built in to the language are described here, roughly organized by topic and significance. Examples ought to be included.
Worth note, this describes not how one ought to use the language, but the operations built into the core of the interpreter. Though many of these operations will be useful in many many conceivable programs, their appearance here is only to document the very core of the language. Everything not detailed here is composed of things here.
core
These operations are generally the most basic and foundational parts of the language, that make the underlying bits work
quote
quote
ought to function in much the same way it does in other lisps. Perhaps it's a bit more complicated and maybe a bit less well-defined, but the idea is the same. Applies to one argument, unevaluated, returns its quoted form. I might change things up with this for various reasons, just a heads up
← (quote asdf) → asdf ← (quote (+ 4 5) → (+ 4 5)
cons
cons
is another standard from lisps, it combines two values into a cons cell. Applies to two arguments, returns the cons cell containing those two values.
← (cons 4 5) → (4 . 5) ← (cons 4 (cons 5 (cons 6 nil))) → (4 5 6)
car
car
takes a cons cell, returns the first element (the 'car') of the pairing. Applies to one argument.
← (car (cons 4 5)) → 4 ← (car (cons (cons 4 5) 6)) → (4 . 5) ← (car (list 1 2 3)) → 1
cdr
cdr
takes a cons cell, returns the second element (the 'cdr') of the pairing. Applies to one argument.
← (cdr (cons 4 5)) → 5 ← (cdr (cons (cons 4 5) 6)) → 6 ← (cdr (list 1 2 3)) → (2 3)
eq
eq
determines whether two things are equal. Applies to two arguments, returns t
if equal and nil
otherwise.
← (eq 4 5) → nil ← (eq 5 (+ 2 3)) → t ← (eq 5 "5") → nil ← (eq (list 1 2 3) (cons 1 (cons 2 (cons 3 nil)))) → t
not
not
applies to one argument. If that argument evaluates to nil
, it returns t
, otherwise it returns nil
.
← (not t) → nil ← (not nil) → t ← (not "truthy value") → nil ← (not (cdr (list 1))) → t
atom
atom
applies to one argument. If that argument evaluates to a cons cell, it returns nil
, otherwise it returns t
← (atom 4) → t ← (atom (+ 4 5)) → t ← (atom (list 1 2 3 4)) → nil ← (atom (cons 3 4)) → nil
rest
rest
is the first 'novel' operation in this language, and (as of now) is the only 'variadic' operation in the language. rest
requires at least two arguments to evaluate. The first argument is a function, which is applied to the rest the arguments collected in a list. That is, (rest f 1 2 3)
evaluates as if it were (f (list 1 2 3))
. Is used to construct other variadic functions (such as list
itself).
← (rest quote 1 2 3) → (1 2 3) ← (rest car 1 2 3) → 1 ← (rest car 1 2 3) → (2 3)
unquote
unquote
takes things out of a quote (if applicable). Applies to a single argument, and functions somewhat as a pseudo-eval
. To be used perhaps sparingly or delicately.
← (unquote 5) → 5 ← (unquote (quote 5)) → 5 ← (unquote (quote (+ 4 5))) → 9
evalarg
evalarg
is a tricky but incredibly useful builtin, and in a sense functions much like a combinator. Given two arguments, it evaluates the second, and puts them back in order, so that the first argument will be called on the evaluated version of the second. Most useful for controlling the order of execution.
← (x-times-n (rand 10) 5) → (0 8 3 9 7) ← (evalarg x-times-n (rand 10) 5) → (2 2 2 2 2)
type
type
takes one argument and returns a string representing that argument's type. Perhaps not what I want a 'type' operator to do in the long run, but for the time being it's pretty swell.
← (type 5) → "uint" ← (type "uint") → "string" ← (type nil) → "nil"
def
It's how we set (global) variables. Applies to two arguments, a symbol and anything else, and sets the symbol to the value of anything else. The background implementation of this is liable to change, so the fun mysterious behaviours of not using this one as described may become more or less interesting.
← (def six (+ 2 4)) → 6 ← (def five 5) → 5 ← (def plus +) → _512 note: the prior line just shows how builtins might be represented in the language ← (plus five six) → 11
exit
This one takes a single argument and exit
s with the argument as the return value. (exit 0)
is the standard way to exit a program or the interpreter without error.
applyn
This is a secret mystery operation. It's not directly accessible, but is called upon tacitly when using uints as functions. It takes first a uint n, then a function, then a final argument, and applies the function to the argument n times.
← (3 cdr (list 1 2 3 4)) → (4) ← (4 (+ 5) 0) → 20
arithmetic
Basic arithmetic operations
+ (add)
Applies to two uints, adds them, returns the sum.
← (+ 4 5) → 9
- (subtract)
Applies to two uints, subtracts them, returns the difference.
← (- 8 5) → 3
* (multiply)
Applies to two uints, multiplies them, returns the product.
← (* 4 5) → 20
/ (divide)
Applies to two uints, does division on them, returns the unsigned integer quotient.
← (/ 36 4) → 9 ← (/ 37 5) -> 7
% (modulus)
Applies to two uints, calculates the value of the first modulo the second, returns it.
← (% 10 3) → 1 ← (% 30 7) → 2
> (greater than)
Applies to two uints, returns t
or nil
depending on whether the first argument is greater than the second.
← (> 10 3) → t ← (> 3 7) → nil ← (> 4 4) → nil
< (less than)
Applies to two uints, returns t
or nil
depending on whether the first argument is less than the second.
← (< 10 3) → nil ← (< 3 7) → t ← (< 4 4) → nil
combinators
This might just be the heart of the language. It's the basics for how more complicated functions are composed, and how things get moved around and operated on. Incredibly useful for many partial application of functions. Heavy inspiration from languages like APL, BQN, and related variants, as well as various Forths and other stack-based languages. They will typically be described in terms of how they transform a sequence of arguments. The bird names are also provided in parenthesis
I
The I
(Idiot) combinator is the identity, it returns whatever it is given. It takes I a
to a
← (I 8) → 8 < (I + 3 5) → 8
S
The S
(Starling) combinator takes S a b c
to a c (b c)
← (S * (+ 4) 3) → 21 ← (S cons cdr (list 3 4)) → ((3 4) 4)
K
The K
(Kestrel) combinator takes two arguments and returns the first, or in other words K a b
to a
← (K 4 5) → 4
B
The B
(Bluebird) is the composition combinator which takes three arguments, and applies the first to the application of the second to the third. B a b c
goes to a (b c)
← (B car cdr (list 1 2 3)) → 2 ← (B (eq 5) car (list 5 6 7)) → t
C
The C
(Cardinal) combinator swaps its second and third arguments, it takes C a b c
to a c b
← (C - 4 5) → 1 ← (C cons 4 5) → (5 . 4)
W
The W
(Warbler) combinator duplicates its second argument, taking W a b
to a b b
← (W + 5) → 10 ← (def square (W *)) → _261 ← (square 7) → 49
Phi
The Phi
(Phoenix) combinator takes Phi a b c d
to a (b d) (c d)
, and is very useful for restructuring lists and cons cells
← (Phi cons cdr car (cons 4 5)) → (5 . 4) ← (Phi * I (+ 1) 7) → 56
Psi
The Psi
combinator takes Psi a b c d
to a (b c) (b d)
← (Psi * (+ 5) 2 3) → 56 ← (Psi cons cdr (list 1 2 3) (list 3 4 5)) → ((2 3) 4 5)
Z
The Z
combinator is probably the most complicated and the most crucial combinator here. It allows for recursion, by taking Z g v
to g (Z g) v
. It is incredibly useful, but also rather difficult to use given the context of the rest of the language at this point. So, enjoy the example below, which is a non-optimized implementation of factorial
← (def fac (Z ((B B) S (C (eq 0) 1) ((B B) S * (C B (C - 1)))))) → _264 ← (fac 5) → 120
io
Operations for printing strings, reading and writing files, things of that nature, all very platform-dependent I'm sure
print
takes one argument, and prints it to standard output, then returns true if it successfuly did so
← (print (list 1 2 3 4 5)) (1 2 3 4 5) → t ← (print "hello, world!") "hello, world!" → t
printstr
printstr
prints a string to standard output (without the quotation marks), returns t
if successfully done, and returns nil
otherwise (or if passed something that's not a string)
← printstr "hello, world!" hello, world! → t ← printstr 45 → nil
pb
pb
prints a builtin with any arguments partially applied in reverse order. Pretty handy for debugging and such, but should probably be avoided otherwise
← (pb +) → _+<nil> ← (pb (+ 5)) → _+<(5)> ← (pb (+ 5 4)) → 9
readfile
readfile
applies to one argument, a string representing the path to a file. If the string does not match a file or cannot open it, it returns nil
, otherwise it returns a string containing the file's contents. Doesn't yet do anything fancy with path expansions or anything like that, gotta be specific
← (readfile "myfile.txt") → "these are the contents of 'myfile.txt"
writefile
writefile
applies to two arguments, a string representing the path to a file, and a string to write into that file. If the file string does not match a file or cannot open or write to it, it returns nil
, otherwise it the number of bytes written. Again, doesn't yet do anything fancy with path expansions or anything like that, gotta be specific
← (writefile "myfile.txt" "writing\na\nstring\nto\nfile") → 24
strings
Operations on strings, nothing more to it
strlen
takes a string and returns its length
← (strlen "hello, world!") → 13 ← (strlen "escape\ncharacters") → 17
strcat
strcat
concatenates two strings, simple as that
← (strcat "hello, " "world!") → "hello, world!"
strat
strat
takes a number n and a string, and returns the nth character of the string. Poorly named, I know
← (strat 3 "test") → "s"
strexpand
takes a string, and returns a list of its characters. Breaks down with unicode
← (strexpand "test") → ("t" "e" "s" "t")
substr
substr
takes two strings, and returns a list of indexes where the first string begins in the second
← (substr "hi" "chili") → (2) ← (substr "oo" "loooong woords") → (2 3 4 10)
strtok
strtok
takes a two strings, and returns a list of substrings of the second string separated by characters in the first
← (strtok " " "hello world") → ("hello" "world") ← (strtok " \n" "showing how\nthis works") → ("showing" "how" "this" "works")
tostr
tostr
takes one argument and returns its representation as a string
← (tostr (cons 45 "hi") → "(45 . "hi")"
meta
This is the messy stuff, if you wanna break things keep reading. Maybe the most interesting bits of messing and extending the language
utob
utob
takes a uint, and returns a builtin whose operation corresponds with the value passed. The numbers will most likely be subject to change with rearrangements or additions
← (utob 512) → _512 ← ((utob 512) 4 5) → 9
btou
btou
takes a builtin operation and returns the uint whose value corresponds with the operation. A sort of 'opcode', if you will
← (btou +) → 512 ← (btou (+ 5)) → 512
parse
parse
parses a string into an s-expression, the same way the repl does it pretty much
← (parse "(cons 4 5)") → ((cons 4 5))
getargs
getargs
takes a builtin, and returns any arguments partially applied to it. These arguments are almost surely unevaluated
← (getargs (+ 5)) → (5) ← (getargs (S + (+ 4))) → ((+ 4) +)
lookup
lookup
looks into the environment to see if the passed argument is defined. Returns nil if undefined, returns a single-element list with its value as the car if it is
← (lookup +) → (_512) ← (lookup notdefined) → nil ← (lookup nil) → (nil)
getenv
getenv
returns the current environment, that is, all the definitions, as an s-expression (which, incidentally, is its internal representation). Must pass it a t
for it to work
← (getenv t) → <some big long list of things>
setenv
Here there be monsters. setenv
allows you to overwrite the environment completely. This is dangerous and you may end up without any sort of meaningfully functional repl if you ever do this. Have fun!