Last active
March 18, 2022 17:42
-
-
Save thesephist/165ea736354f953cf72958464f1d70eb to your computer and use it in GitHub Desktop.
An interpreter for Klisp (Scheme-like lisp flavor) in ~500L of Oak.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env oak | |
// A Klisp written in Oak | |
// ported over from the Ink implementation at thesephist/klisp. | |
{ | |
println: println | |
default: default | |
clone: clone | |
slice: slice | |
map: map | |
find: find | |
reduce: reduce | |
} := import('std') | |
{ | |
join: join | |
replace: replace | |
trim: trim | |
endsWith?: endsWith? | |
} := import('str') | |
fmt := import('fmt') | |
fs := import('fs') | |
// alias print, because it will be overridden later | |
oakPrint := print | |
// like std.reduce, but works on lisp lists | |
fn reduceSexpr(L, init, f) { | |
fn sub(acc, node) if node { | |
? -> acc | |
_ -> sub(f(acc, node.0), node.1) | |
} | |
sub(init, L) | |
} | |
fn Reader(s) { | |
i := 0 | |
fn peek() s.(i) | |
fn next { | |
c := peek() | |
i <- i + 1 | |
c | |
} | |
fn nextSpan { | |
fn sub(acc) if peek() { | |
?, ' ', '\n', '\t', '(', ')' -> acc | |
_ -> sub(acc << next()) | |
} | |
sub('') | |
} | |
fn forward { | |
fn sub() if peek() { | |
' ', '\n', '\t' -> { | |
next() | |
sub() | |
} | |
// ignore comments | |
';' -> { | |
fn sub() if next() { | |
? -> ? | |
'\n' -> forward() | |
_ -> sub() | |
} | |
sub() | |
} | |
} | |
sub() | |
} | |
{ | |
peek: peek | |
next: next | |
nextSpan: nextSpan | |
forward: forward | |
} | |
} | |
fn read(s) { | |
r := Reader(s |> trim()) | |
{ | |
peek: peek | |
next: next | |
nextSpan: nextSpan | |
forward: forward | |
} := r | |
// forward through possible comments at start | |
forward() | |
fn parse() if c := peek() { | |
? -> ? // eof | |
')' -> ? // halt parsing | |
',' -> { | |
next() | |
forward() | |
[:quote, [parse(), ?]] | |
} | |
'\'' -> { | |
next() | |
fn sub(acc) if peek() { | |
? -> acc | |
'\\' -> { | |
next() | |
sub(acc << next) | |
} | |
'\'' -> { | |
next() | |
forward() | |
acc | |
} | |
_ -> sub(acc << next()) | |
} | |
sub('') | |
} | |
'(' -> { | |
next() | |
forward() | |
fn sub(acc, tail) if peek() { | |
? -> acc | |
')' -> { | |
next() | |
acc | |
} | |
'.' -> { | |
next() | |
forward() | |
cons := parse() | |
forward() | |
acc := if acc { | |
? -> cons | |
_ -> { | |
tail.1 := cons | |
acc | |
} | |
} | |
sub(acc, cons) | |
} | |
_ -> { | |
cons := [parse(), ?] | |
forward() | |
acc := if acc { | |
? -> cons | |
_ -> { | |
tail.1 := cons | |
acc | |
} | |
} | |
sub(acc, cons) | |
} | |
} | |
sub(?, ?) | |
} | |
_ -> { | |
span := nextSpan() | |
forward() | |
if n := float(span) { | |
? -> atom(span) | |
_ -> n | |
} | |
} | |
} | |
term := [parse(), ?] | |
prog := [:do, term] | |
fn sub(tail) if peek() { | |
?, ')' -> prog | |
_ -> { | |
term := [parse(), ?] | |
tail.1 := term | |
forward() | |
sub(term) | |
} | |
} | |
sub(term) | |
} | |
// globally unique sentinel value | |
LispNull := fn {} | |
fn getv(env, name) if v := env.(string(name)) { | |
? -> if e := env.'-env' { | |
? -> ? | |
_ -> getv(e, string(name)) | |
} | |
LispNull -> ? | |
_ -> v | |
} | |
fn setv(env, name, v) if v { | |
? -> env.(string(name)) := LispNull | |
_ -> env.(string(name)) := v | |
} | |
fn makeFn(f, L) [false, f, L] | |
fn makeMacro(f, L) [true, f, L] | |
fn makeNative(f) makeFn(f, ?) | |
fn eval(L, env) if type(L) { | |
:list -> if L.0 { | |
:quote -> L.(1).0 | |
:def -> { | |
name := L.(1).0 | |
val := eval(L.(1).(1).0, env) | |
setv(env, name, val) | |
val | |
} | |
:do -> { | |
fn sub(form) if form.1 { | |
? -> eval(form.0, env) | |
_ -> { | |
eval(form.0, env) | |
sub(form.1) | |
} | |
} | |
sub(L.1) | |
} | |
:if -> { | |
cond := L.(1).0 | |
conseq := L.(1).(1).0 | |
altern := L.(1).(1).(1).0 | |
eval( | |
if eval(cond, env) { | |
true -> conseq | |
_ -> altern | |
} | |
env | |
) | |
} | |
:fn -> { | |
params := L.(1).0 | |
body := L.(1).(1).0 | |
makeFn(fn(args) eval( | |
body | |
{ | |
fn sub(envc, params, args) if { | |
params = ?, args = ? -> envc | |
_ -> { | |
setv(envc, params.0, args.0) | |
sub(envc, params.1, args.1) | |
} | |
} | |
sub({ '-env': env }, params, args) | |
} | |
), L) | |
} | |
:macro -> { | |
params := L.(1).0 | |
body := L.(1).(1).0 | |
makeMacro(fn(args) eval( | |
body | |
{ | |
fn sub(envc, params, args) if { | |
params = ?, args = ? -> envc | |
_ -> { | |
setv(envc, params.0, args.0) | |
sub(envc, params.1, args.1) | |
} | |
} | |
sub({ '-env': env }, params, [args, ?]) | |
} | |
), L) | |
} | |
:expand -> if expr := eval(L.(1).0, env) { | |
? -> expr | |
_ -> if funcStub := eval(expr.0, env) { | |
[_, _, _] -> if funcStub.0 { | |
true -> eval(funcStub.1, env)(expr.1) | |
_ -> expr | |
} | |
_ -> expr | |
} | |
} | |
_ -> { | |
argcs := L.1 | |
funcStub := eval(L.0, env) | |
func := eval(funcStub.1, env) | |
// funcStub.0 reports whether the stub is a macro | |
if funcStub.0 { | |
true -> func(argcs) |> eval(env) | |
_ -> { | |
args := [?] | |
argcs |> with reduceSexpr(args) fn(head, x) { | |
cons := [eval(x, env)] | |
head.1 := cons | |
cons | |
} | |
func(args.1) | |
} | |
} | |
} | |
} | |
:atom -> getv(env, L) | |
_ -> L | |
} | |
Env := { | |
'true': true | |
'false': false | |
'car': makeNative(fn(L) L.(0).0) | |
'cdr': makeNative(fn(L) L.(0).1) | |
'cons': makeNative(fn(L) [L.0, L.(1).0]) | |
'len': makeNative(fn(L) if type(x := L.0) { | |
:string -> len(x) | |
:atom -> len(string(x)) | |
_ -> 0 | |
}) | |
'gets': makeNative(fn(L) if type(s := L.0) { | |
:string -> slice(s, L.(1).0, L.(1).(1).0) | |
_ -> '' | |
}) | |
'sets!': makeNative(fn(L) if type(s := L.0) { | |
:string -> { | |
idx := L.(1).0 | |
s.(idx) := slice(L.(1).(1).0, 0, len(s) - idx) | |
} | |
_ -> '' | |
}) | |
// ports of Oak's builtins | |
'char': makeNative(fn(L) char(int(L.0))) | |
'point': makeNative(fn(L) codepoint(L.0)) | |
'sin': makeNative(fn(L) sin(L.0)) | |
'cos': makeNative(fn(L) cos(L.0)) | |
'tan': makeNative(fn(L) tan(L.0)) | |
'floor': makeNative(fn(L) int(L.0)) | |
'rand': makeNative(rand) | |
'time': makeNative(time) | |
'args': makeNative(fn(_) { | |
fn makeList(xs) if len(xs) { | |
0 -> ? | |
_ -> [xs.0, makeList(xs |> slice(1))] | |
} | |
makeList(args()) | |
}) | |
// arithmetic and logical operators | |
'=': makeNative(fn(L) L.1 |> reduceSexpr(true, fn(acc, x) acc & L.0 = x)) | |
'<': makeNative(fn(L) if L { | |
? -> true | |
_ -> { | |
last := L.0 | |
L.1 |> reduceSexpr(true, fn(acc, x) { | |
y := acc & last < x | |
last <- x | |
y | |
}) | |
} | |
}) | |
'>': makeNative(fn(L) if L { | |
? -> true | |
_ -> { | |
last := L.0 | |
L.1 |> reduceSexpr(true, fn(acc, x) { | |
y := acc & last > x | |
last <- x | |
y | |
}) | |
} | |
}) | |
'+': makeNative(fn(L) L.1 |> reduceSexpr(L.0, fn(a, b) a + b)) | |
'-': makeNative(fn(L) L.1 |> reduceSexpr(L.0, fn(a, b) a - b)) | |
'*': makeNative(fn(L) L.1 |> reduceSexpr(L.0, fn(a, b) a * b)) | |
'/': makeNative(fn(L) L.1 |> reduceSexpr(L.0, fn(a, b) a / b)) | |
'%': makeNative(fn(L) L.1 |> reduceSexpr(L.0, fn(a, b) a % b)) | |
'#': makeNative(fn(L) L.1 |> reduceSexpr(L.0, pow)) | |
// types and conversions | |
'type': makeNative(fn(L) if L.0 { | |
[_, _, _] -> 'function' | |
[_, _] -> 'list' | |
_ -> if ty := type(L.0) { | |
:atom -> 'symbol' | |
:int, :float -> 'number' | |
_ -> string(ty) | |
} | |
}) | |
'string->number': makeNative(fn(L) number(L.0) |> default(0)) | |
'number->string': makeNative(fn(L) string(L.0)) | |
'string->symbol': makeNative(fn(L) atom(L.0)) | |
'symbol->string': makeNative(fn(L) string(L.0)) | |
// I/O, system | |
'print': makeNative(fn(L) { | |
output := L.1 |> reduceSexpr( | |
if type(L.0) { | |
:string -> L.0 | |
_ -> print(L.0) | |
} | |
fn(a, b) a + ' ' + if type(b) { | |
:string -> b | |
_ -> print(b) | |
} | |
) | |
oakPrint(output) | |
}) | |
} | |
// we override "print" for the interpreter, but also need to call print. | |
// So we alias it to "out". | |
out := print | |
fn print(L) if L { | |
[_, _] -> { | |
fn sub(term, acc) if term { | |
[_, [_, _]] -> sub(term.1, acc << print(term.0)) | |
[_, ?] -> acc << print(term.0) | |
[_, _] -> sub(term.1, acc << print(term.0) << '.') | |
_ -> acc << print(term) | |
} | |
'(' << join(sub(L, []), ' ') << ')' | |
} | |
[_, _, _] -> if L.2 { | |
? -> '(function)' | |
_ -> print(L.2) | |
} | |
_ -> if type(L) { | |
:null -> '()' | |
:atom -> string(L) | |
:string -> '\'' << (L |> replace('\\', '\\\\') |> replace('\'', '\\\'')) << '\'' | |
_ -> string(L) | |
} | |
} | |
LibKlisp := ' | |
; core library | |
; cons cell shorthands | |
(def caar | |
(fn (x) (car (car x)))) | |
(def cadr | |
(fn (x) (car (cdr x)))) | |
(def cdar | |
(fn (x) (cdr (car x)))) | |
(def cddr | |
(fn (x) (cdr (cdr x)))) | |
; lazy-evaluating boolean combinators | |
(def ! | |
(fn (x) | |
(if x false true))) | |
(def & | |
(macro (terms) | |
(if (= terms ()) | |
,true | |
(cons ,if | |
(cons (car terms) | |
(cons (cons ,& (cdr terms)) | |
(cons ,false ()))))))) | |
(def | | |
(macro (terms) | |
(if (= terms ()) | |
,false | |
(cons ,if | |
(cons (car terms) | |
(cons ,true | |
(cons (cons ,| (cdr terms)) | |
()))))))) | |
(def ^ | |
(macro (terms) | |
(cons ,! | |
(cons (cons ,= terms) | |
())))) | |
(def eq? =) | |
(def not !) | |
(def and &) | |
(def or |) | |
(def != ^) | |
(def xor ^) | |
(def neq? ^) | |
; type assertions | |
(def nil? | |
(fn (x) (= x ()))) | |
(def zero? | |
(fn (x) (= x 0))) | |
(def number? | |
(fn (x) (= (type x) \'number\'))) | |
(def boolean? | |
(fn (x) (= (type x) \'boolean\'))) | |
(def string? | |
(fn (x) (= (type x) \'string\'))) | |
(def symbol? | |
(fn (x) (= (type x) \'symbol\'))) | |
(def function? | |
(fn (x) (= (type x) \'function\'))) | |
(def list? | |
(fn (x) (= (type x) \'list\'))) | |
(def pair? | |
(fn (x) | |
(& (list? x) | |
(= (size x) 2)))) | |
; identity | |
(def id | |
(fn (x) x)) | |
(def gensym | |
(fn () | |
(-> (rand) | |
(* 100000000) | |
floor | |
number->string | |
((fn (s) (+ \'sym\' s))) | |
string->symbol))) | |
; basic math | |
(def neg | |
(fn (n) (- 0 n))) | |
(def neg? | |
(fn (n) (< n 0))) | |
(def abs | |
(fn (n) | |
(if (neg? n) | |
(neg n) | |
n))) | |
(def sign | |
(fn (n) | |
(if (neg? n) | |
(neg 1) | |
1))) | |
(def sqrt | |
(fn (n) (# n 0.5))) | |
(def even? | |
(fn (n) (zero? (% n 2)))) | |
(def odd? | |
(fn (n) (! (even? n)))) | |
(def >= | |
(fn (a b) (! (< a b)))) | |
(def <= | |
(fn (a b) (! (> a b)))) | |
(def inc | |
(fn (n) (+ n 1))) | |
(def dec | |
(fn (n) (- n 1))) | |
(def sum | |
(fn (ns) (reduce ns + 0))) | |
(def prod | |
(fn (ns) (reduce ns * 1))) | |
(def times | |
; repeat x, n times in a list | |
(fn (n x) | |
(map (range 0 n 1) | |
(fn () x)))) | |
; macros | |
(def when | |
(macro (terms) | |
(list ,if (car terms) (cadr terms) ()))) | |
(def unless | |
(macro (terms) | |
(list ,if (car terms) () (cadr terms)))) | |
(def let | |
(macro (terms) | |
(do | |
(def decl (car terms)) | |
(def declname (car decl)) | |
(def declval (cadr decl)) | |
(def body (cadr terms)) | |
(list | |
(list ,fn (list declname) body) | |
declval)))) | |
(def list | |
(macro (items) | |
((def -list | |
(fn (items) | |
(if (nil? items) | |
() | |
(cons ,cons | |
(cons (car items) | |
(cons (-list (cdr items)) | |
())))))) | |
items))) | |
(def quasiquote | |
(macro (terms) | |
(cons | |
,list | |
(map (car terms) | |
(fn (term) | |
(if (list? term) | |
(if (= ,unquote (car term)) | |
(cadr term) | |
(list ,quasiquote term)) | |
(list ,quote term))))))) | |
(def do-times | |
(macro (terms) | |
(cons ,do | |
(times (car terms) (list (cadr terms)))))) | |
; (while condition . body) | |
(def while | |
(macro (terms) | |
(do | |
(def cndn (car terms)) | |
(def body (cdr terms)) | |
(def -while-f (gensym)) | |
(quasiquote | |
((def (unquote -while-f) | |
(fn () | |
(if (unquote cndn) | |
(do | |
(unquote (cons ,do body)) | |
((unquote -while-f))) | |
())))))))) | |
; shorthand for defining functions in scope | |
(def defn | |
(macro (terms) | |
(quasiquote | |
(def (unquote (car terms)) | |
(fn (unquote (cadr terms)) | |
(unquote (car (cddr terms)))))))) | |
; (cond (pred body) (pred body) (default-body)) | |
(def cond | |
(macro (terms) | |
((def -cond | |
(fn (terms) | |
(if (nil? terms) | |
() | |
(if (nil? (cdar terms)) | |
(caar terms) | |
(quasiquote | |
(if (unquote (car (car terms))) | |
(unquote (cadr (car terms))) | |
(unquote (-cond (cdr terms))))))))) | |
terms))) | |
; (match val (tag body) (tag body) (default-body)) | |
(def match | |
(macro (terms) | |
(do | |
(def -match-val (gensym)) | |
(def -match | |
(fn (terms) | |
(if (nil? terms) | |
() | |
(if (nil? (cdar terms)) | |
(caar terms) | |
(quasiquote | |
(if (= (unquote -match-val) (unquote (car (car terms)))) | |
(unquote (cadr (car terms))) | |
(unquote (-match (cdr terms))))))))) | |
(quasiquote | |
(let ((unquote -match-val) (unquote (car terms))) | |
(unquote (-match (cdr terms)))))))) | |
; thread-first | |
(def -> | |
(macro (terms) | |
(do | |
(def apply-partials | |
(fn (partials expr) | |
(if (nil? partials) | |
expr | |
(if (symbol? (car partials)) | |
(list (car partials) | |
(apply-partials (cdr partials) expr)) | |
(cons (caar partials) | |
(cons (apply-partials (cdr partials) expr) | |
(cdar partials))))))) | |
(apply-partials (reverse (cdr terms)) | |
(car terms))))) | |
; thread-last | |
(def ->> | |
(macro (terms) | |
(do | |
(def apply-partials | |
(fn (partials expr) | |
(if (nil? partials) | |
expr | |
(if (symbol? (car partials)) | |
(list (car partials) | |
(apply-partials (cdr partials) expr)) | |
(append (car partials) | |
(apply-partials (cdr partials) expr)))))) | |
(apply-partials (reverse (cdr terms)) | |
(car terms))))) | |
; partial application | |
(def partial | |
(macro (terms) | |
(let (-partial-arg (gensym)) | |
(list ,fn | |
(cons -partial-arg ()) | |
(map (car terms) | |
(fn (x) | |
(if (= x ,_) -partial-arg x))))))) | |
; macro expansion functions and macros | |
(def macroexpand | |
(macro (terms) | |
(quasiquote (expand (quote (unquote (car terms))))))) | |
(def expand-all | |
(fn (expr) | |
(if (list? expr) | |
(let (expanded (expand expr)) | |
(if (list? expanded) | |
(map expanded expand-all) | |
expanded)) | |
expr))) | |
(def macroexpand-all | |
(macro (terms) | |
(quasiquote (expand-all (quote (unquote (car terms))))))) | |
; list methods | |
(def nth | |
(fn (xs i) | |
(if (zero? i) | |
(car xs) | |
(nth (cdr xs) (dec i))))) | |
(def nth? | |
(fn (xs i x) | |
(if (zero? i) | |
(= (car xs) x) | |
(nth? (cdr xs) (dec i) x)))) | |
(def last | |
(fn (xs) | |
(if (nil? xs) | |
() | |
(if (nil? (cdr xs)) | |
(car xs) | |
(last (cdr xs)))))) | |
(def index | |
(fn (xs x) | |
(do | |
(def index-from | |
(fn (xs x rest) | |
(if (nil? xs) | |
(neg 1) | |
(if (= (car xs) x) | |
rest | |
(index-from (cdr xs) x (inc rest)))))) | |
(index-from xs x 0)))) | |
(def find | |
(fn (xs f?) | |
(if (nil? xs) | |
() | |
(if (f? (car xs)) | |
(car xs) | |
(find (cdr xs) f?))))) | |
(def some? | |
(fn (xs) | |
(if (nil? xs) | |
false | |
(if (car xs) | |
true | |
(some? (cdr xs)))))) | |
(def every? | |
(fn (xs) | |
(if (nil? xs) | |
true | |
(if (car xs) | |
(every? (cdr xs)) | |
false)))) | |
(def min | |
(fn (xs) | |
(if (nil? xs) | |
() | |
(reduce xs | |
(fn (a b) | |
(if (< a b) a b)) | |
(car xs))))) | |
(def max | |
(fn (xs) | |
(if (nil? xs) | |
() | |
(reduce xs | |
(fn (a b) | |
(if (< a b) b a)) | |
(car xs))))) | |
(def contains? | |
(fn (xs x) | |
(<= 0 (index xs x)))) | |
; O(n^2) behavior with linked lists | |
(def append | |
(fn (xs el) | |
(if (nil? xs) | |
(list el) | |
(cons (car xs) | |
(append (cdr xs) el))))) | |
(def join | |
(fn (xs ys) | |
(if (nil? xs) | |
ys | |
(cons (car xs) | |
(if (nil? (cdr xs)) | |
ys | |
(join (cdr xs) ys)))))) | |
(def range | |
(fn (start end step) | |
; intentionally avoiding then when macro for efficiency | |
(if (< start end) | |
(cons start | |
(range (+ start step) end step)) | |
()))) | |
(def seq | |
(fn (n) (range 0 n 1))) | |
(def nat | |
(fn (n) (range 1 (inc n) 1))) | |
(def reverse | |
(fn (x) | |
(if (nil? x) | |
x | |
(append (reverse (cdr x)) | |
(car x))))) | |
(def map | |
(fn (xs f) | |
(if (nil? xs) | |
() | |
(cons (f (car xs)) | |
(map (cdr xs) f))))) | |
(def map-deep | |
(fn (xs f) | |
(map xs (fn (x) | |
(if (list? x) | |
(map-deep x f) | |
(f x)))))) | |
(def reduce | |
(fn (xs f acc) | |
(if (nil? xs) | |
acc | |
(reduce (cdr xs) f (f acc (car xs)))))) | |
(def filter | |
(fn (xs f) | |
(if (nil? xs) | |
() | |
(if (f (car xs)) | |
(cons (car xs) | |
(filter (cdr xs) f)) | |
(filter (cdr xs) f))))) | |
(def each | |
(fn (xs f) | |
(if (nil? xs) | |
() | |
(do | |
(f (car xs)) | |
(each (cdr xs) f))))) | |
(def size | |
(fn (xs) | |
(if (nil? xs) | |
0 | |
(inc (size (cdr xs)))))) | |
(def zip-with | |
(fn (xs ys f) | |
(if (| (nil? xs) (nil? ys)) | |
() | |
(cons (f (car xs) (car ys)) | |
(zip-with (cdr xs) (cdr ys) f))))) | |
(def zip | |
(fn (xs ys) | |
(zip-with xs ys list))) | |
(def take | |
(fn (xs n) | |
(if (| (nil? xs) (zero? n)) | |
() | |
(cons (car xs) | |
(take (cdr xs) (dec n)))))) | |
(def drop | |
(fn (xs n) | |
(if (| (nil? xs) (zero? n)) | |
xs | |
(drop (cdr xs) (dec n))))) | |
(def flatten | |
(fn (xs) | |
(reduce xs join ()))) | |
(def partition | |
(fn (xs n) | |
(if (nil? xs) | |
() | |
(cons (take xs n) | |
(partition (drop xs n) n))))) | |
; string functions | |
(def cat | |
(fn (xs joiner) | |
(if (nil? xs) | |
\'\' | |
(do | |
(def cat-onto | |
(fn (xs prefix) | |
(if (nil? xs) | |
prefix | |
(cat-onto (cdr xs) | |
(+ prefix joiner (car xs)))))) | |
(cat-onto (cdr xs) (car xs)))))) | |
(defn char-at (s i) | |
(gets s i (inc i))) | |
; composites: persistent immutable associative array | |
; | |
; comps store key-value pairs in a list as | |
; ((key . value) (key . value) (key . value)) for O(n) lookup and O(1) insert. | |
; Each entry is a single cons cell rather than a list to make value lookup a bit | |
; more efficient. | |
(def comp | |
(macro (terms) | |
(do | |
(def -comp | |
(fn (items) | |
(if (nil? items) | |
() | |
(list ,cons | |
(list ,cons (car items) (cadr items)) | |
(-comp (cddr items)))))) | |
(-comp terms)))) | |
; recursive value lookup by key | |
(def getc | |
(fn (cp k) | |
(if (nil? cp) | |
() | |
(if (= k (caar cp)) | |
(cdar cp) | |
(getc (cdr cp) k))))) | |
; comps are immutable, and new values are set by adding new entries | |
; to the head of the comp\'s underlying list. setc does not modify the | |
; given comp and returns a new comp with the new key, value set. | |
(def setc | |
(fn (cp k v) | |
(cons (cons k v) cp))) | |
; get just the comp keys | |
(def keys | |
(fn (cp) | |
(map cp car))) | |
; get just the comp values | |
(def values | |
(fn (cp) | |
(map cp cdr))) | |
; utilities | |
(def println | |
(macro (terms) | |
; we expand the macro manually here | |
; because println should be as fast as possible | |
(cons ,do | |
(cons (cons ,print terms) | |
(cons ,(print (char 10)) | |
()))))) | |
(def comment | |
; add "(comment val)" to an expr head | |
; to substitute the expr with "val" | |
(macro (terms) (car terms))) | |
(def log-runtime | |
; prints runtime (finish - start) of an expression | |
(macro (terms) | |
(let (-val (gensym)) | |
(quasiquote | |
(do | |
(def start (time)) | |
(def (unquote -val) (unquote (cadr terms))) | |
(println (+ \'Runtime for \' (unquote (car terms)) \':\') | |
(number->string (* 1000 (- (time) start))) | |
\'ms\') | |
(unquote -val)))))) | |
' | |
LibMath := ' | |
; math library | |
; depends on klisp.klisp | |
; Euclid\'s GCD algorithm | |
(defn gcd (a b) | |
; prereq: a < b | |
(do | |
(defn sub (a b) | |
(if (zero? a) | |
b | |
(sub (% b a) a))) | |
(def a (abs a)) | |
(def b (abs b)) | |
(if (> a b) | |
(sub b a) | |
(sub a b)))) | |
; LCM using GCD | |
(defn lcm (a b) | |
(* a (/ b (gcd a b)))) | |
(defn factor? (n c) | |
(zero? (% n c))) | |
; prime filter | |
(defn prime? (n) | |
(if (< n 2) | |
false | |
(do | |
(def max (inc (floor (sqrt n)))) | |
(defn sub (i) | |
(if (= i max) | |
true | |
(if (factor? n i) | |
false | |
(sub (inc i))))) | |
(sub 2)))) | |
; prime factorize natural number | |
(defn prime-factors (n) | |
(do | |
(defn sub (pfs m pf) | |
(if (= m 1) | |
pfs | |
(if (factor? m pf) | |
(sub (cons pf pfs) | |
(/ m pf) | |
pf) | |
(sub pfs | |
m | |
(inc pf))))) | |
(reverse (sub () n 2)))) | |
; naive factorize | |
(defn factors (n) | |
(let (first-half (-> (nat (floor (sqrt n))) | |
(filter (partial (factor? n _))))) | |
(cond | |
((nil? first-half) first-half) | |
((nil? (cdr first-half)) first-half) | |
(true | |
(join first-half | |
(let (rev-first-half (reverse first-half)) | |
(if (= (car rev-first-half) | |
(/ n (car rev-first-half))) | |
(cdr (map rev-first-half (partial (/ n _)))) | |
(map rev-first-half (partial (/ n _)))))))))) | |
(defn randi (max) | |
(floor (* (rand) max))) | |
(defn mean (xs) | |
(/ (sum xs) (size xs))) | |
(def avg mean) | |
(defn geomean (xs) | |
(# (prod xs) (/ 1 (size xs)))) | |
' | |
fn loadLibs(libs, withEnv) { | |
fn sub(i, env) if i { | |
len(libs) -> withEnv(env) | |
_ -> with fs.readFile(libs.(i)) fn(file) { | |
file |> read() |> eval(env) | |
sub(i + 1, env) | |
} | |
} | |
sub(0, Env |> clone()) | |
} | |
fn loadLibSources(libs, withEnv) { | |
fn sub(i, env) if i { | |
len(libs) -> withEnv(env) | |
_ -> { | |
file := libs.(i) | |
file |> read() |> eval(env) | |
sub(i + 1, env) | |
} | |
} | |
sub(0, Env |> clone()) | |
} | |
fn loadStdlib(withEnv) loadLibSources([ | |
LibKlisp | |
LibMath | |
], withEnv) | |
// CLI | |
Version := '0.1-oak' | |
with loadStdlib() fn(env) { | |
lispFileIdx := if idx := args() |> find(fn(arg) arg |> endsWith?('.klisp') | arg |> endsWith?('.lisp')) { | |
-1 -> default(2) | |
_ -> idx | |
} | |
if path := args().(lispFileIdx) { | |
? -> { | |
fmt.printf('Klisp interpreter v{{0}}.', Version) | |
fn sub(env) { | |
out('> ') | |
with input() fn(evt) if evt.type { | |
:error -> println('EOF.') | |
_ -> { | |
evt.data |> | |
read() |> | |
eval(env) |> | |
print() |> | |
println() | |
sub(env) | |
} | |
} | |
} | |
sub(env) | |
} | |
_ -> with fs.readFile(path) fn(file) if file { | |
? -> fmt.printf('error: could not read {{0}}', path) | |
_ -> file |> read() |> eval(env) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment