1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-20 07:34:49 +00:00
janet/src/compiler/boot.dst

1181 lines
32 KiB
Plaintext
Raw Normal View History

# Bootstrap the dst environment
# Copyright 2018 (C) Calvin Rose
###
###
### Macros and Basic Functions
###
###
2018-03-24 05:44:17 +00:00
(var *env*
"The current environment."
2018-03-18 18:01:58 +00:00
_env)
(def defn :macro
"Define a function. Equivalent to (def name (fn name [args] ...))."
2018-03-24 05:44:17 +00:00
(fn [name & more]
(def len (length more))
(def fstart (fn recur [i]
(def ith (ast.unwrap1 (get more i)))
(def t (type ith))
(def tuple? (= t :tuple))
(def array? (= t :array))
2018-05-05 18:41:47 +00:00
(if (if tuple? tuple? array?) i
(if (< i len) (recur (+ i 1))))))
(def start (fstart 0))
(def fnbody (tuple.prepend (tuple.prepend (tuple.slice more start) name) 'fn))
(def formargs (array.concat @['def name] (array.slice more 0 start) @[fnbody]))
(apply1 tuple formargs)))
(def defmacro :macro
"Define a macro."
(do
2018-03-18 18:01:58 +00:00
(def defn* (get (get _env 'defn) :value))
2018-05-05 18:41:47 +00:00
(fn [name & more]
(apply1 defn* (array.concat
@[name :macro] more)))))
(defmacro defmacro-
"Define a private macro that will not be exported."
[name & more]
(apply1 tuple (array.concat
@['defmacro name :private] more)))
2018-03-12 04:57:13 +00:00
(defmacro defn-
"Define a private function that will not be exported."
[name & more]
(apply1 tuple (array.concat
@['defn name :private] more)))
2018-03-12 04:57:13 +00:00
(defmacro def-
"Define a private value that will not be exported."
[name & more]
(apply1 tuple (array.concat
@['def name :private] more)))
# Basic predicates
2018-03-15 01:46:56 +00:00
(defn even? [x] (== 0 (% x 2)))
(defn odd? [x] (== 1 (% x 2)))
(defn zero? [x] (== x 0))
(defn pos? [x] (> x 0))
(defn neg? [x] (< x 0))
2018-03-15 01:46:56 +00:00
(defn one? [x] (== x 1))
(defn integer? [x] (= (type x) :integer))
(defn real? [x] (= (type x) :real))
2018-05-19 02:18:34 +00:00
(defn number? [x]
(def t (type x))
(if (= t :integer) true (= t :real)))
(defn fiber? [x] (= (type x) :fiber))
(defn string? [x] (= (type x) :string))
(defn symbol? [x] (= (type x) :symbol))
2018-05-26 17:46:27 +00:00
(defn keyword? [x] (if (not= (type x) :symbol) nil (= 58 (get x 0))))
(defn buffer? [x] (= (type x) :buffer))
(defn function? [x] (= (type x) :function))
(defn cfunction? [x] (= (type x) :cfunction))
(defn abstract? [x] (= (type x) :abstract))
(defn table? [x] (= (type x) :table ))
(defn struct? [x] (= (type x) :struct))
(defn array? [x] (= (type x) :array))
(defn tuple? [x] (= (type x) :tuple))
(defn boolean? [x] (= (type x) :boolean))
2018-05-19 02:18:34 +00:00
(defn bytes? [x]
(def t (type x))
(if (= t :string) true (if (= t :symbol) true (= t :buffer))))
2018-05-19 02:18:34 +00:00
(defn dictionary? [x]
(def t (type x))
(if (= t :table) true (= t :struct)))
2018-03-29 01:16:12 +00:00
(defn indexed? [x]
(def t (type x))
(if (= t :array) true (= t :tuple)))
(defn callable? [x]
2018-05-05 18:41:47 +00:00
(def t (type x))
(if (= t :function) true (= t :cfunction)))
(defn true? [x] (= x true))
(defn false? [x] (= x false))
(defn nil? [x] (= x nil))
(def atomic? (do
(def non-atomic-types {
:array true
:tuple true
:table true
:struct true
})
(fn [x] (not (get non-atomic-types (type x))))))
2018-03-15 01:46:56 +00:00
(defn sum [xs] (apply1 + xs))
(defn product [xs] (apply1 * xs))
# C style macros and functions for imperative sugar
(defn inc [x] (+ x 1))
(defn dec [x] (- x 1))
(defmacro ++ [x] (tuple ':= x (tuple + x 1)))
(defmacro -- [x] (tuple ':= x (tuple - x 1)))
(defmacro += [x n] (tuple ':= x (tuple + x n)))
(defmacro -= [x n] (tuple ':= x (tuple - x n)))
(defmacro *= [x n] (tuple ':= x (tuple * x n)))
(defmacro /= [x n] (tuple ':= x (tuple / x n)))
(defmacro %= [x n] (tuple ':= x (tuple % x n)))
(defmacro &= [x n] (tuple ':= x (tuple & x n)))
(defmacro |= [x n] (tuple ':= x (tuple | x n)))
(defmacro ^= [x n] (tuple ':= x (tuple ^ x n)))
(defmacro >>= [x n] (tuple ':= x (tuple >> x n)))
(defmacro <<= [x n] (tuple ':= x (tuple << x n)))
(defmacro >>>= [x n] (tuple ':= x (tuple >>> x n)))
2018-05-02 03:38:53 +00:00
(defmacro default
"Define a default value for an optional argument.
Expands to (def sym (if (= nil sym) val sym))"
[sym val]
(tuple 'def sym (tuple 'if (tuple = nil sym) val sym)))
2018-03-12 06:06:51 +00:00
(defmacro comment
"Ignores the body of the comment."
[])
(defmacro if-not
2018-04-30 22:11:19 +00:00
"Shorthand for (if (not ... "
[condition exp-1 exp-2]
(tuple 'if condition exp-2 exp-1))
(defmacro when
2018-03-12 06:06:51 +00:00
"Evaluates the body when the condition is true. Otherwise returns nil."
[condition & body]
(tuple 'if condition (tuple.prepend body 'do)))
(defmacro when-not
2018-04-30 22:11:19 +00:00
"Shorthand for (when (not ... "
[condition & body]
(tuple 'if condition nil (tuple.prepend body 'do)))
(defmacro cond
2018-03-12 06:06:51 +00:00
"Evaluates conditions sequentially until the first true condition
is found, and then executes the corresponding body. If there are an
odd number of forms, the last expression is executed if no forms
are matched. If there are no matches, return nil."
[& pairs]
(defn aux [i]
(def restlen (- (length pairs) i))
(if (= restlen 0) nil
(if (= restlen 1) (get pairs i)
(tuple 'if (get pairs i)
(get pairs (+ i 1))
(aux (+ i 2))))))
(aux 0))
2018-03-16 17:40:10 +00:00
(defn doc*
[env sym]
(def x (get env sym))
(if (not x)
(print "symbol " x " not found.")
(do
(def d (get x 'doc))
(print "\n" (if d d "no documentation found.") "\n"))))
2018-03-16 17:40:10 +00:00
(defmacro doc
"Shows documentation for the given symbol."
[sym]
2018-03-16 19:58:11 +00:00
(tuple doc* '_env (tuple 'quote sym)))
2018-03-16 17:40:10 +00:00
(defn apply
"Evaluate to (f ...args), where the final value of args must be an array or
tuple and will be spliced into the function call. For example, (apply + 1 2 @[3 4])
evaluates to 10."
[f & args]
(def last (- (length args) 1))
(apply1 f (array.concat (array.slice args 0 -2) (get args last))))
(defmacro switch
2018-03-12 06:06:51 +00:00
"Select the body that equals the dispatch value. When pairs
has an odd number of arguments, the last is the default expression.
If no match is found, returns nil"
[dispatch & pairs]
(def atm (atomic? (ast.unwrap1 dispatch)))
(def sym (if atm dispatch (gensym)))
(defn aux [i]
(def restlen (- (length pairs) i))
(if (= restlen 0) nil
(if (= restlen 1) (get pairs i)
(tuple 'if (tuple = sym (get pairs i))
(get pairs (+ i 1))
(aux (+ i 2))))))
(if atm
(aux 0)
(tuple 'do
(tuple 'def sym dispatch)
(aux 0))))
(defmacro let
"Create a scope and bind values to symbols. Each pair in bindings is
assigned as if with def, and the body of the let form returns the last
value."
[bindings & body]
(def head (ast.unwrap1 bindings))
(if (odd? (length head)) (error "expected even number of bindings to let"))
(def len (length head))
(var i 0)
(var accum @['do])
(while (< i len)
(array.push accum (tuple 'def
2018-03-11 20:30:38 +00:00
(get head i)
(get head (+ 1 i))))
(+= i 2))
(array.concat accum body)
(apply1 tuple accum))
2018-02-03 22:22:04 +00:00
(defmacro loop
"A general purpose loop macro."
[head & body]
(def head1 (ast.unwrap1 head))
(def len (length head1))
(defn doone
2018-05-26 17:46:27 +00:00
[i preds]
(default preds @['and])
(if (>= i len)
(tuple.prepend body 'do)
(do
(def bindings (get head1 i))
2018-05-26 17:46:27 +00:00
(def ubindings (ast.unwrap1 bindings))
(def verb (ast.unwrap1 (get head1 (+ i 1))))
(def object (ast.unwrap1 (get head1 (+ i 2))))
2018-05-26 17:46:27 +00:00
(if (keyword? ubindings)
(switch
ubindings
:while (do
(array.push preds verb)
(doone (+ i 2) preds))
:let (tuple 'let verb (doone (+ i 2)))
:when (tuple 'if verb (doone (+ i 2)))
(error ("unexpected loop predicate: " verb)))
(switch
verb
:range (do
(def [start end _inc] (ast.unwrap1 object))
(def inc (if _inc _inc 1))
(def endsym (gensym))
2018-05-26 17:46:27 +00:00
(def preds @['and (tuple < bindings endsym)])
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'var bindings start)
(tuple 'def endsym end)
2018-05-26 17:46:27 +00:00
(tuple 'while (apply1 tuple preds)
subloop
(tuple ':= bindings (tuple + bindings inc)))))
:keys (do
(def $dict (gensym "dict"))
2018-05-26 17:46:27 +00:00
(def preds @['and (tuple not= nil bindings)])
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'def $dict object)
(tuple 'var bindings (tuple next $dict nil))
2018-05-26 17:46:27 +00:00
(tuple 'while (apply1 tuple preds)
subloop
(tuple ':= bindings (tuple next $dict bindings)))))
:in (do
(def $len (gensym "len"))
(def $i (gensym "i"))
(def $indexed (gensym "indexed"))
2018-05-26 17:46:27 +00:00
(def preds @['and (tuple < $i $len)])
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'def $indexed object)
(tuple 'def $len (tuple length $indexed))
(tuple 'var $i 0)
2018-05-26 17:46:27 +00:00
(tuple 'while (apply1 tuple preds)
(tuple 'def bindings (tuple get $indexed $i))
2018-05-26 17:46:27 +00:00
subloop
(tuple ':= $i (tuple + 1 $i)))))
(error ("unexpected loop verb: " verb)))))))
(doone 0))
2018-05-05 18:41:47 +00:00
(defmacro for
"Similar to loop, but accumulates the loop body into an array and returns that."
[head & body]
(def $accum (gensym "accum"))
(tuple 'do
(tuple 'def $accum @[])
(tuple 'loop head
(tuple array.push $accum
(tuple.prepend body 'do)))
$accum))
(defmacro and
"Evaluates to the last argument if all preceding elements are true, otherwise
evaluates to false."
[& forms]
(def len (length forms))
(if (= len 0) true ((fn aux [i]
(cond
(>= (inc i) len) (get forms i)
(tuple 'if (get forms i) (aux (inc i)) false))) 0)))
(defmacro or
"Evaluates to the last argument if all preceding elements are false, otherwise
evaluates to true."
[& forms]
(def len (length forms))
(if (= len 0) false ((fn aux [i]
(def fi (get forms i))
(if
(>= (inc i) len) fi
(do
(if (atomic? (ast.unwrap1 fi))
(tuple 'if fi fi (aux (inc i)))
(do
(def $fi (gensym))
(tuple 'do (tuple 'def $fi fi)
(tuple 'if $fi $fi (aux (inc i))))))))) 0)))
(defmacro coro
"A wrapper for making fibers. Same as (fiber (fn [] ...body))."
[& body]
(tuple fiber.new (apply tuple 'fn [] body)))
(defmacro if-let
"Takes the first one or two forms in a vector and if both are true binds
all the forms with let and evaluates the first expression else
evaluates the second"
[bindings tru fal]
(def bindings (ast.unwrap1 bindings))
(def len (length bindings))
(if (zero? len) (error "expected at least 1 binding"))
(if (odd? len) (error "expected an even number of bindings"))
(defn aux [i]
(def bl (get bindings i))
(def br (get bindings (+ 1 i)))
(if (>= i len)
tru
(do
(def atm (atomic? (ast.unwrap1 bl)))
(def sym (if atm bl (gensym)))
2018-05-05 18:41:47 +00:00
(if atm
# Simple binding
(tuple 'do
(tuple 'def sym br)
(tuple 'if sym (aux (+ 2 i)) fal))
# Destructured binding
(tuple 'do
(tuple 'def sym br)
(tuple 'if sym
2018-05-05 18:41:47 +00:00
(tuple 'do
(tuple 'def bl sym)
(aux (+ 2 i)))
fal))))))
(aux 0))
(defmacro when-let
"Takes the first one or two forms in vector and if true binds
all the forms with let and evaluates the body"
[bindings & body]
(tuple 'if-let bindings (tuple.prepend body 'do)))
(defn comp
"Takes multiple functions and returns a function that is the composition
of those functions."
[& functions]
(switch (length functions)
0 nil
1 (get functions 0)
2 (let [[f g] functions] (fn [x] (f (g x))))
3 (let [[f g h] functions] (fn [x] (f (g (h x)))))
4 (let [[f g h i] functions] (fn [x] (f (g (h (i x))))))
(let [[f g h i j] functions]
(apply comp (fn [x] (f (g (h (i (j x))))))
(tuple.slice functions 5 -1)))))
(defn identity
"A function that returns its first argument."
[x]
x)
(defn complement
"Returns a function that is the complement to the argument."
[f]
(fn [x] (not (f x))))
(defn extreme
"Returns the most extreme value in args based on the orderer order.
Returns nil if args is empty."
[order args]
(def len (length args))
(when (pos? len)
(var ret (get args 0))
(loop [i :range [0 len]]
(def v (get args i))
(if (order v ret) (:= ret v)))
ret))
(defn max [& args] (extreme > args))
(defn min [& args] (extreme < args))
(defn max-order [& args] (extreme order> args))
(defn min-order [& args] (extreme order< args))
###
###
2018-03-28 20:38:05 +00:00
### Indexed Combinators
###
###
2018-05-05 18:41:47 +00:00
(def sort
2018-03-28 20:38:05 +00:00
"Sort an array in-place. Uses quicksort and is not a stable sort."
(do
(defn partition
[a lo hi by]
(def pivot (get a hi))
(var i lo)
(loop [j :range [lo hi]]
2018-03-28 20:38:05 +00:00
(def aj (get a j))
(when (by aj pivot)
(def ai (get a i))
(put a i aj)
(put a j ai)
(++ i)))
(put a hi (get a i))
(put a i pivot)
i)
(defn sort-help
[a lo hi by]
(when (> hi lo)
(def piv (partition a lo hi by))
(sort-help a lo (- piv 1) by)
(sort-help a (+ piv 1) hi by))
a)
(fn [a by]
(sort-help a 0 (- (length a) 1) (or by order<)))))
(defn sorted
"Returns the sorted version of an indexed data structure."
[ind by]
(def sa (sort (apply1 array ind) by))
(if (= :tuple (type ind))
(apply1 tuple sa)
sa))
(defn reduce
2018-04-30 22:11:19 +00:00
"Reduce, also know as fold-left in many languages, transforms
an indexed type (array, tuple) with a function to produce a value."
[f init ind]
(var res init)
(loop [x :in ind]
(:= res (f res x)))
2018-03-26 17:36:58 +00:00
res)
(defn map
"Map a function over every element in an array or tuple and return
the same type as the input sequence."
[f & inds]
(def ninds (length inds))
(if (= 0 ninds) (error "expected at least 1 indexed collection."))
(var limit (length (get inds 0)))
(loop [i :range [0 ninds]]
(def l (length (get inds i)))
(if (< l limit) (:= limit l)))
(def [i1 i2 i3 i4] inds)
2018-05-18 20:24:09 +00:00
(def res (array.new limit))
(switch ninds
1 (loop [i :range [0 limit]] (array.push res (f (get i1 i))))
2 (loop [i :range [0 limit]] (array.push res (f (get i1 i) (get i2 i))))
3 (loop [i :range [0 limit]] (array.push res (f (get i1 i) (get i2 i) (get i3 i))))
4 (loop [i :range [0 limit]] (array.push res (f (get i1 i) (get i2 i) (get i3 i) (get i4 i))))
(loop [i :range [0 limit]]
2018-05-18 20:24:09 +00:00
(def args (array.new ninds))
(loop [j :range [0 ninds]] (array.push args (get (get inds j) i)))
(array.push res (apply1 f args))))
res)
2018-05-05 18:41:47 +00:00
(defn each
"Map a function over every element in an array or tuple but do not
return a new indexed type."
[f & inds]
(def ninds (length inds))
(if (= 0 ninds) (error "expected at least 1 indexed collection."))
(var limit (length (get inds 0)))
(loop [i :range [0 ninds]]
2018-05-05 18:41:47 +00:00
(def l (length (get inds i)))
(if (< l limit) (:= limit l)))
(def [i1 i2 i3 i4] inds)
(switch ninds
1 (loop [i :range [0 limit]] (f (get i1 i)))
2 (loop [i :range [0 limit]] (f (get i1 i) (get i2 i)))
3 (loop [i :range [0 limit]] (f (get i1 i) (get i2 i) (get i3 i)))
4 (loop [i :range [0 limit]] (f (get i1 i) (get i2 i) (get i3 i) (get i4 i)))
(loop [i :range [0 limit]]
2018-05-18 20:24:09 +00:00
(def args (array.new ninds))
(loop [j :range [0 ninds]] (array.push args (get (get inds j) i)))
2018-05-05 18:41:47 +00:00
(apply1 f args))))
(defn mapcat
2018-05-05 18:41:47 +00:00
"Map a function over every element in an array or tuple and
2018-04-30 22:11:19 +00:00
use array to concatenate the results. Returns the same
type as the input sequence."
[f ind t]
(def res @[])
(loop [x :in ind]
(array.concat res (f x)))
(if (= :tuple (type (or t ind)))
(apply1 tuple res)
res))
(defn filter
"Given a predicate, take only elements from an array or tuple for
which (pred element) is truthy. Returns the same type as the input sequence."
[pred ind t]
(def res @[])
(loop [item :in ind]
(if (pred item)
(array.push res item)))
(if (= :tuple (type (or t ind)))
(apply1 tuple res)
res))
2018-05-18 20:24:09 +00:00
(defn range
"Create an array of values [0, n)."
[n]
(def arr (array.new n))
(loop [i :range [0 n]] (put arr i i))
2018-05-18 20:24:09 +00:00
arr)
(defn find-index
"Find the index of indexed type for which pred is true. Returns nil if not found."
[pred ind]
(def len (length ind))
(var i 0)
(var going true)
(while (if (< i len) going)
(def item (get ind i))
(if (pred item) (:= going false) (++ i)))
(if going nil i))
(defn find
2018-04-30 22:11:19 +00:00
"Find the first value in an indexed collection that satisfies a predicate. Returns
nil if not found. Note their is no way to differentiate a nil from the indexed collection
and a not found. Consider find-index if this is an issue."
[pred ind]
(get ind (find-index pred ind)))
(defn take-until
2018-04-30 22:11:19 +00:00
"Given a predicate, take only elements from an indexed type that satisfy
the predicate, and abort on first failure. Returns a new indexed type that is
the same type as the input."
[pred ind t]
(def i (find-index pred ind))
(if (= :tuple (type (or t ind)))
(tuple.slice ind 0 i)
(array.slice ind 0 i)))
(defn take-while
"Same as (take-until (complement pred) ind t)."
[pred ind t]
(take-until (complement pred) ind t))
(defn drop-until
2018-04-30 22:11:19 +00:00
"Given a predicate, remove elements from an indexed type that satisfy
the predicate, and abort on first failure."
[pred ind t]
(def i (find-index pred ind))
(if (= :tuple (type (or t ind)))
(tuple.slice ind i -1)
(array.slice ind i -1)))
(defn drop-while
"Same as (drop-until (complement pred) ind t)."
[pred ind t]
(drop-until (complement pred) ind t))
(defn juxt*
[& funs]
(fn [& args]
(def ret @[])
(loop [f :in funs]
(array.push ret (apply1 f args)))
(apply1 tuple ret)))
(defmacro juxt
[& funs]
(def parts @['tuple])
(def $args (gensym))
(loop [f :in funs]
(array.push parts (tuple apply1 f $args)))
(tuple 'fn (tuple '& $args) (apply1 tuple parts)))
2018-03-12 16:47:05 +00:00
(defmacro ->
"Threading macro. Inserts x as the second value in the first form
in form, and inserts the modified firsts form into the second form
in the same manner, and so on. Useful for expressing pipelines of data."
2018-03-12 16:47:05 +00:00
[x & forms]
(defn fop [last nextform]
(def n (ast.unwrap1 nextform))
2018-03-12 16:47:05 +00:00
(def [h t] (if (= :tuple (type n))
[tuple (get n 0) (array.slice n 1)]
[tuple n @[]]))
(def parts (array.concat @[h last] t))
(apply1 tuple parts))
2018-03-12 16:47:05 +00:00
(reduce fop x forms))
(defmacro ->>
"Threading macro. Inserts x as the last value in the first form
in form, and inserts the modified firsts form into the second form
in the same manner, and so on. Useful for expressing pipelines of data."
2018-03-12 16:47:05 +00:00
[x & forms]
(defn fop [last nextform]
(def n (ast.unwrap1 nextform))
2018-03-12 16:47:05 +00:00
(def [h t] (if (= :tuple (type n))
[tuple (get n 0) (array.slice n 1)]
[tuple n @[]]))
(def parts (array.concat @[h] t @[last]))
(apply1 tuple parts))
2018-03-12 16:47:05 +00:00
(reduce fop x forms))
2018-05-05 18:41:47 +00:00
(defn partial
"Partial function application."
[f & more]
(if (zero? (length more)) f
(fn [& r] (apply1 f (array.concat @[] more r)))))
(defn every? [pred seq]
(var res true)
(var i 0)
(def len (length seq))
(while (< i len)
(def item (get seq i))
(if (pred item)
(++ i)
(do (:= res false) (:= i len))))
res)
(defn array.reverse
"Reverses the order of the elements in a given array or tuple and returns a new array."
[t]
(var n (dec (length t)))
(var reversed @[])
(while (>= n 0)
(array.push reversed (get t n))
(-- n))
reversed)
(defn tuple.reverse
"Reverses the order of the elements given an array or tuple and returns a tuple"
[t]
(apply1 tuple (array.reverse t)))
(defn reverse
"Reverses order of elements in a given array or tuple"
[t]
((switch (type t)
:tuple tuple.reverse
:array array.reverse) t))
(defn zipcoll
2018-03-24 05:44:17 +00:00
"Creates an table or tuple from two arrays/tuples. If a third argument of
:struct is given result is struct else is table."
[keys vals t]
(def res @{})
(def lk (length keys))
(def lv (length vals))
(def len (if (< lk lv) lk lv))
(loop [i :range [0 len]]
(put res (get keys i) (get vals i)))
(if (= :struct t)
(table.to-struct res)
res))
(defn update
2018-03-24 05:44:17 +00:00
"Accepts a key argument and passes its' associated value to a function.
The key then, is associated to the function's return value"
[coll a-key a-function & args]
(def old-value (get coll a-key))
(put coll a-key (apply a-function old-value args)))
(defn merge
2018-04-30 22:11:19 +00:00
"Merges multiple tables/structs to one. If a key appears in more than one
collection, then later values replace any previous ones.
The type of the first collection determines the type of the resulting
collection"
[& colls]
(def container @{})
(loop [c :in colls
key :keys c]
(put container key (get c key)))
(if (table? (get colls 0)) container (table.to-struct container)))
2018-03-29 01:16:12 +00:00
(defn keys
"Get the keys of an associative data structure."
[x]
(def arr (array.new (length x)))
2018-03-29 01:16:12 +00:00
(var k (next x nil))
(while (not= nil k)
(array.push arr k)
2018-03-29 01:16:12 +00:00
(:= k (next x k)))
arr)
(defn values
"Get the values of an associative data structure."
[x]
(def arr (array.new (length x)))
2018-03-29 01:16:12 +00:00
(var k (next x nil))
(while (not= nil k)
(array.push arr (get x k))
2018-03-29 01:16:12 +00:00
(:= k (next x k)))
arr)
(defn pairs
"Get the values of an associative data structure."
[x]
(def arr (array.new (length x)))
2018-03-29 01:16:12 +00:00
(var k (next x nil))
(while (not= nil k)
(array.push arr (tuple k (get x k)))
2018-03-29 01:16:12 +00:00
(:= k (next x k)))
arr)
2018-06-03 18:21:24 +00:00
(defn frequencies
"Get the number of occurences of each value in a indexed structure."
[ind]
(def freqs @{})
(loop
[x :in ind]
(def n (get freqs x))
(put freqs x (if n (+ 1 n) 1)))
freqs)
###
###
### Pretty Printer
###
###
2018-05-05 18:41:47 +00:00
(defn pp
"Pretty print a value. Displays values inside collections, and is safe
to call on any table. Does not print table prototype information."
[x]
2018-03-26 17:36:58 +00:00
(def buf @"")
(def indent @"\n")
(def seen @{})
(var nextid 0)
# Forward declaration
(var recur nil)
(defn do-ds
[y start end checkcycle dispatch]
(def id (get seen y))
(if (and checkcycle id)
(do
(buffer.push-string buf "<cycle ")
(buffer.push-string buf (string id))
(buffer.push-string buf ">"))
2018-03-14 23:08:00 +00:00
(do
2018-03-26 17:36:58 +00:00
(put seen y (++ nextid))
(buffer.push-string buf start)
2018-03-26 17:36:58 +00:00
(dispatch y)
(buffer.push-string buf end))))
2018-03-26 17:36:58 +00:00
(defn pp-seq [y]
(def len (length y))
(if (< len 5)
(do
(loop [i :range [0 len]]
(when (not= i 0) (buffer.push-string buf " "))
2018-03-26 17:36:58 +00:00
(recur (get y i))))
2018-03-14 23:08:00 +00:00
(do
(buffer.push-string indent " ")
(loop [i :range [0 len]]
(when (not= i len) (buffer.push-string buf indent))
2018-03-26 17:36:58 +00:00
(recur (get y i)))
(buffer.popn indent 2)
(buffer.push-string buf indent))))
2018-03-26 17:36:58 +00:00
(defn pp-dict-nested [y]
(buffer.push-string indent " ")
(loop [[k v] :in (sort (pairs y))]
(buffer.push-string buf indent)
2018-03-26 17:36:58 +00:00
(recur k)
(buffer.push-string buf " ")
2018-03-29 01:16:12 +00:00
(recur v))
(buffer.popn indent 2)
(buffer.push-string buf indent))
2018-03-26 17:36:58 +00:00
(defn pp-dict-simple [y]
(var i -1)
(loop [[k v] :in (sort (pairs y))]
(if (pos? (++ i)) (buffer.push-string buf " "))
2018-03-26 17:36:58 +00:00
(recur k)
(buffer.push-string buf " ")
2018-03-29 01:16:12 +00:00
(recur v)))
2018-03-26 17:36:58 +00:00
(defn pp-dict [y]
(def complex? (> (length y) 4))
((if complex? pp-dict-nested pp-dict-simple) y))
2018-03-26 17:36:58 +00:00
(def printers {
:array (fn [y] (do-ds y "@[" "]" true pp-seq))
:tuple (fn [y] (do-ds y "(" ")" false pp-seq))
:table (fn [y] (do-ds y "@{" "}" true pp-dict))
:struct (fn [y] (do-ds y "{" "}" false pp-dict))
2018-03-14 23:08:00 +00:00
})
2018-05-05 18:41:47 +00:00
2018-03-26 17:36:58 +00:00
(:= recur (fn [y]
(def p (get printers (type y)))
(if p
(p y)
(buffer.push-string buf (describe y)))))
2018-03-14 23:08:00 +00:00
2018-03-26 17:36:58 +00:00
(recur x)
(buffer.push-string buf "\n")
2018-03-14 23:08:00 +00:00
(file.write stdout buf)
nil)
2018-03-14 23:08:00 +00:00
###
###
### Macro Expansion
###
###
2018-03-15 01:46:56 +00:00
(defn macroexpand-1
2018-03-18 18:01:58 +00:00
"Expand macros in a form, but do not recursively expand macros."
[x]
2018-03-24 05:44:17 +00:00
(defn doarray [a]
(map macroexpand-1 a))
2018-03-18 18:01:58 +00:00
(defn dotable [t]
(def newt @{})
(var key (next t nil))
(while (not= nil key)
(put newt (macroexpand-1 key) (macroexpand-1 (get t key)))
2018-03-18 18:01:58 +00:00
(:= key (next t key)))
newt)
(defn expandlast [t]
(def len (length t))
(def last (get t (- len 1)))
(tuple.append (tuple.slice t 0 -2) (macroexpand-1 last)))
2018-03-18 18:01:58 +00:00
(defn expandall [t]
(def args (doarray (tuple.slice t 1)))
2018-03-18 18:01:58 +00:00
(apply tuple (get t 0) args))
(defn expandfn [t]
(def args (doarray (tuple.slice t 2)))
2018-03-18 18:01:58 +00:00
(apply tuple 'fn (get t 1) args))
(def specs {
':= expandlast
'ast-quote identity
'def expandlast
'do expandall
'fn expandfn
'if expandall
'quote identity
'var expandlast
'while expandall
2018-03-24 05:44:17 +00:00
})
2018-03-18 18:01:58 +00:00
(defn dotup [t]
(def h (get t 0))
(def s (get specs h))
(def entry (get *env* h))
(def m (get entry :value))
(def m? (get entry :macro))
(cond
s (s t)
m? (apply1 m (tuple.slice t 1))
2018-03-18 18:01:58 +00:00
(apply1 tuple (doarray t))))
(defn doarray* [a]
(def res (doarray a))
(if (= (apply tuple res) (apply tuple a)) a res))
(defn dotable* [t]
(def res (dotable t))
(if (= (table.to-struct res) (table.to-struct t)) t res))
2018-03-18 18:01:58 +00:00
(def ux (ast.unwrap1 x))
(switch (type ux)
2018-03-18 18:01:58 +00:00
:tuple (dotup ux)
:array (doarray* ux)
:struct (table.to-struct (dotable ux))
2018-03-18 18:01:58 +00:00
:table (dotable* ux)
ux))
(defn macroexpand
"Expand macros completely."
[x]
(var previous x)
(var current (macroexpand-1 x))
2018-03-18 18:01:58 +00:00
(var counter 0)
(while (not= current previous)
(if (> (++ counter) 200)
(error "macro expansion too nested"))
(:= previous current)
(:= current (macroexpand-1 current)))
2018-03-18 18:01:58 +00:00
current)
2018-03-29 01:16:12 +00:00
###
###
### Evaluation and Compilation
###
###
(defn make-env [parent]
(def parent (if parent parent _env))
(def newenv (table.setproto @{} parent))
(put newenv '_env @{:value newenv :private true})
newenv)
(defn run-context
2018-03-12 06:06:51 +00:00
"Run a context. This evaluates expressions of dst in an environment,
and is encapsulates the parsing, compilation, and evaluation of dst.
env is the environment to evaluate the code in, chunks is a function
that returns strings or buffers of source code (from a repl, file,
network connection, etc. onvalue and onerr are callbacks that are
invoked when a result is returned and when an error is produced,
respectively.
2018-03-24 05:44:17 +00:00
2018-04-30 22:11:19 +00:00
This function can be used to implement a repl very easily, simply
2018-03-12 06:06:51 +00:00
pass a function that reads line from stdin to chunks, and print to
onvalue."
[env chunks onvalue onerr]
# Are we done yet?
(var going true)
# The parser object
(def p (parser.new 1))
# Fiber stream of characters
(def chars (coro
(def buf @"")
(var len 1)
(while (< 0 len)
(buffer.clear buf)
(chunks buf p)
2018-03-16 22:15:34 +00:00
(:= len (length buf))
(loop [i :range [0 len]]
(fiber.yield (get buf i))))
0))
# Fiber stream of values
(def vals (coro
2018-03-26 00:39:38 +00:00
(while going
(switch (parser.status p)
:full (fiber.yield (parser.produce p))
:error (onerr "parse" (parser.error p))
(switch (fiber.status chars)
:new (parser.byte p (fiber.resume chars))
:pending (parser.byte p (fiber.resume chars))
2018-03-26 00:39:38 +00:00
(:= going false))))
(when (not= :root (parser.status p))
(onerr "parse" "unexpected end of source"))))
# Evaluate 1 source form
(defn eval1 [source]
2018-03-18 13:13:21 +00:00
(var good true)
(def f (fiber.new (fn []
(def res (compile source env))
(if (= (type res) :function)
(res)
2018-03-18 13:13:21 +00:00
(do
(:= good false)
(onerr "compile" (get res :error))))) :a))
(def res (fiber.resume f))
(when good
(def sig (fiber.status f))
(if going
(if (= sig :dead)
(onvalue res)
(onerr "runtime" res f)))))
# Run loop
2018-03-26 00:39:38 +00:00
(def oldenv *env*)
(:= *env* env)
(while going (eval1 (fiber.resume vals)))
2018-03-26 00:39:38 +00:00
(:= *env* oldenv)
env)
(defn default-error-handler
[t x f]
(file.write stdout (string t " error: "))
(if (bytes? x)
(do (file.write stdout x)
(file.write stdout "\n"))
(pp x))
(when f
(def st (fiber.stack f))
(loop [{
:function func
:tail tail
:pc pc
:c c
:name name
} :in st]
(file.write stdout " in")
(when c (file.write stdout " cfunction"))
(when name (file.write stdout (string " " name)))
(when func (file.write stdout (string " " func)))
(when pc (file.write stdout (string " (pc=" pc ")")))
(when tail (file.write stdout " (tailcall)"))
(file.write stdout "\n"))))
2018-05-19 02:18:34 +00:00
(defn eval
"Evaluates a string in the current environment. If more control over the
environment is needed, use run-context."
[str]
(var state (string str))
2018-05-19 02:18:34 +00:00
(defn chunks [buf]
(def ret state)
(:= state nil)
(if ret
(buffer.push-string buf ret)))
(var returnval nil)
(run-context *env* chunks (fn [x] (:= returnval x)) default-error-handler)
returnval)
(def module.paths @[
"./?.dst"
"./?/init.dst"
"./dst_modules/?.dst"
"./dst_modules/?/init.dst"
"/usr/local/dst/0.0.0/?.dst"
"/usr/local/dst/0.0.0/?/init.dst"
])
(def module.native-paths @[
"./?.so"
2018-05-20 01:29:22 +00:00
"./?/??.so"
"./dst_modules/?.so"
2018-05-20 01:29:22 +00:00
"./dst_modules/?/??.so"
"/usr/local/dst/0.0.0/?.so"
2018-05-20 01:29:22 +00:00
"/usr/local/dst/0.0.0/?/??.so"
])
2018-05-20 01:29:22 +00:00
(defn module.find
[path paths]
(def parts (string.split "." path))
(def last (get parts (- (length parts) 1)))
(def normname (string.replace-all "." "/" path))
(array.push
(map (fn [x]
(def y (string.replace "??" last x))
(string.replace "?" normname y))
paths)
path))
2018-05-19 02:18:34 +00:00
(def require
"Require a module with the given name. Will search all of the paths in
module.paths, then the path as a raw file path. Returns the new environment
2018-05-19 02:18:34 +00:00
returned from compiling and running the file."
(do
(defn check-mod
[f testpath]
(if f f (file.open testpath)))
(defn find-mod [path]
2018-05-20 01:29:22 +00:00
(def paths (module.find path module.paths))
(reduce check-mod nil paths))
(defn check-native
[p testpath]
(if p p (do
(def f (file.open testpath))
(if f (do (file.close f) testpath)))))
(defn find-native [path]
2018-05-20 01:29:22 +00:00
(def paths (module.find path module.native-paths))
(reduce check-native nil paths))
(def cache @{})
(def loading @{})
2018-06-08 19:58:23 +00:00
(fn [path args]
(when (get loading path)
(error (string "circular dependency: module " path " is loading")))
2018-06-08 19:58:23 +00:00
(def {
:exit exit-on-error
} args)
(def check (get cache path))
(if check check (do
(def newenv (make-env))
(put cache path newenv)
(put loading path true)
(def f (find-mod path))
(if f
(do
# Normal dst module
(defn chunks [buf] (file.read f 1024 buf))
2018-06-08 19:58:23 +00:00
(run-context newenv chunks identity
(if exit-on-error
(fn [a b c] (default-error-handler a b c) (os.exit 1))
default-error-handler))
(file.close f)
(put loading path nil)
newenv)
(do
# Try native module
(def n (find-native path))
(if (not n)
(error (string "could not open file for module " path)))
((native n)))))))))
2018-03-16 17:40:10 +00:00
(defn import* [env path & args]
2018-06-08 19:58:23 +00:00
(def targs (apply1 table args))
(def {
:as as
:prefix prefix
2018-06-08 19:58:23 +00:00
} targs)
(def newenv (require path targs))
(var k (next newenv nil))
(def {:meta meta} newenv)
(def prefix (or (and as (string as ".")) prefix (string path ".")))
(while k
(def v (get newenv k))
(when (not (get v :private))
(def newv (table.setproto @{:private true} v))
(put env (symbol prefix k) newv))
(:= k (next newenv k))))
2018-03-16 17:40:10 +00:00
(defmacro import [path & args]
"Import a module. First requires the module, and then merges its
symbols into the current environment, prepending a given prefix as needed.
(use the :as or :prefix option to set a prefix). If no prefix is provided,
use the name of the module as a prefix."
(def upath (string (ast.unwrap path)))
2018-05-19 02:18:34 +00:00
(def argm (map (fn [x]
(if (and (symbol? x) (= (get x 0) 58))
x
(string x)))
(ast.unwrap args)))
(apply tuple import* '_env upath argm))
2018-05-07 05:04:24 +00:00
(defn repl [getchunk onvalue onerr]
"Run a repl. The first parameter is an optional function to call to
2018-04-30 22:11:19 +00:00
get a chunk of source code. Should return nil for end of file."
(def newenv (make-env))
2018-05-07 05:04:24 +00:00
(default getchunk (fn [buf]
(file.read stdin :line buf)))
2018-05-07 05:04:24 +00:00
(default onvalue (fn [x]
(put newenv '_ @{:value x})
2018-05-07 05:04:24 +00:00
(pp x)))
(default onerr default-error-handler)
(run-context newenv getchunk onvalue onerr))
(defn all-symbols
"Get all symbols available in the current environment."
[env]
(default env *env*)
(def envs @[])
(do (var e env) (while e (array.push envs e) (:= e (table.getproto e))))
(array.reverse envs)
(def symbol-set @{})
(defn onenv [envi]
(defn onk [k]
(put symbol-set k true))
(each onk (keys envi)))
(each onenv envs)
(sort (keys symbol-set)))