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 cons car cdr eq not atom rest unquote evalarg type def exit applyn

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 exits 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 sub mul div mod gt lt

+ (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 S K B C W Phi Psi Z

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 printstr pb readfile writefile

print

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 strcat strat strexpand substr strtok tostr

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 btou parse lookup getargs getenv setenv

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!