1
0
mirror of https://github.com/janet-lang/janet synced 2024-07-01 17:43:15 +00:00
janet/src/core/core.janet

1530 lines
45 KiB
Plaintext
Raw Normal View History

2018-09-06 02:18:42 +00:00
# The core janet library
# Copyright 2018 (C) Calvin Rose
###
###
### Macros and Basic Functions
###
###
2018-03-24 05:44:17 +00:00
(var *env*
"The current environment."
_env)
2018-03-18 18:01:58 +00:00
(def defn :macro
"(def name & more)\n\nDefine a function. Equivalent to (def name (fn name [args] ...))."
2018-07-01 15:52:15 +00:00
(fn defn [name & more]
(def len (length more))
(def modifiers @[])
(var docstr "")
(def fstart
(fn recur [i]
(def {i ith} more)
(def t (type ith))
(if (= t :tuple)
i
(do
(if (= t :string)
(:= docstr ith)
(array/push modifiers ith))
(if (< i len) (recur (+ i 1)))))))
(def start (fstart 0))
(def args more.start)
# Add function signature to docstring
(var index 0)
(def arglen (length args))
(def buf (buffer "(" name))
(while (< index arglen)
(buffer/push-string buf " ")
(string/pretty args.index 4 buf)
(:= index (+ index 1)))
(array/push modifiers (string buf ")\n\n" docstr))
# Build return value
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
(defn defmacro :macro
"Define a macro."
[name & more]
(apply defn name :macro more))
(defmacro defmacro-
"Define a private macro that will not be exported."
[name & more]
(apply defn name :macro :private more))
2018-03-12 04:57:13 +00:00
(defmacro defn-
"Define a private function that will not be exported."
[name & more]
(apply 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]
~(def ,name :private ,;more))
(defn defglobal
"Dynamically create a global def."
[name value]
(def name* (symbol name))
(put *env* name* @{:value value})
nil)
(defn varglobal
"Dynamically create a global var."
[name init]
(def name* (symbol name))
(put *env* name* @{:ref @[init]})
nil)
# Basic predicates
(defn even? "Check if x is even." [x] (== 0 (% x 2)))
(defn odd? "Check if x is odd." [x] (not= 0 (% x 2)))
(defn zero? "Check if x is zero." [x] (== x 0))
(defn pos? "Check if x is greater than 0." [x] (> x 0))
(defn neg? "Check if x is less than 0." [x] (< x 0))
(defn one? "Check if x is equal to 1." [x] (== x 1))
(defn integer? "Check if x is an integer." [x] (= (type x) :integer))
(defn real? [x] "Check if x is a real number." (= (type x) :real))
(defn number? "Check if x is a number." [x]
(def t (type x))
(if (= t :integer) true (= t :real)))
(defn fiber? "Check if x is a fiber." [x] (= (type x) :fiber))
(defn string? "Check if x is a string." [x] (= (type x) :string))
(defn symbol? "Check if x is a symbol." [x] (= (type x) :symbol))
(defn keyword? "Check if x is a keyword style symbol."
[x]
(if (not= (type x) :symbol) nil (= 58 x.0)))
(defn buffer? "Check if x is a buffer." [x] (= (type x) :buffer))
(defn function? "Check if x is a function (not a cfunction)."
[x] (= (type x) :function))
(defn cfunction? "Check if x a cfunction." [x] (= (type x) :cfunction))
(defn table? [x] "Check if x a table." (= (type x) :table ))
(defn struct? [x] "Check if x a struct." (= (type x) :struct))
(defn array? [x] "Check if x is an array." (= (type x) :array))
(defn tuple? [x] "Check if x is a tuple." (= (type x) :tuple))
(defn boolean? [x] "Check if x is a boolean." (= (type x) :boolean))
(defn bytes? "Check if x is a string, symbol, or buffer." [x]
(def t (type x))
(if (= t :string) true (if (= t :symbol) true (= t :buffer))))
(defn dictionary? "Check if x a table or struct." [x]
(def t (type x))
(if (= t :table) true (= t :struct)))
(defn indexed? "Check if x is an array or tuple." [x]
(def t (type x))
(if (= t :array) true (= t :tuple)))
(defn callable? "Check if x is a function or cfunction." [x]
(def t (type x))
(if (= t :function) true (= t :cfunction)))
(defn true? "Check if x is true." [x] (= x true))
(defn false? "Check if x is false." [x] (= x false))
(defn nil? "Check if x is nil." [x] (= x nil))
(defn empty? "Check if xs is empty." [xs] (= 0 (length xs)))
2018-12-08 15:53:22 +00:00
(def idempotent?
"(idempotent? x)\n\nCheck if x is a value that evaluates to itself when compiled."
(do
(def non-atomic-types
{:array true
:tuple true
:table true
:buffer true
:struct true})
2018-12-08 15:53:22 +00:00
(fn idempotent? [x] (not (get non-atomic-types (type x))))))
(defmacro with-idemp
"Return janet code body that has been prepended
with a binding of form to atom. If form is a non-
idempotent form (a function call, etc.), make sure the resulting
code will only call evaluate once, even if body contains multiple
copies of binding. In body, use binding instead of form."
[binding form & body]
(def $result (gensym))
(def $form (gensym))
~(do
(def ,$form ,form)
(def ,binding (if (idempotent? ,$form) ,$form (gensym)))
(def ,$result (do ,;body))
(if (= ,$form ,binding)
,$result
(tuple 'do (tuple 'def ,binding ,$form) ,$result))))
# C style macros and functions for imperative sugar
(defn inc "Returns x + 1." [x] (+ x 1))
(defn dec "Returns x - 1." [x] (- x 1))
(defmacro ++ "Increments the var x by 1." [x] ~(:= ,x (,+ ,x ,1)))
(defmacro -- "Decrements the var x by 1." [x] ~(:= ,x (,- ,x ,1)))
(defmacro += "Increments the var x by n." [x n] ~(:= ,x (,+ ,x ,n)))
(defmacro -= "Decrements the vat x by n." [x n] ~(:= ,x (,- ,x ,n)))
(defmacro *= "Shorthand for (:= x (* x n))." [x n] ~(:= ,x (,* ,x ,n)))
(defmacro /= "Shorthand for (:= x (/ x n))." [x n] ~(:= ,x (,/ ,x ,n)))
(defmacro %= "Shorthand for (:= x (% x n))." [x n] ~(:= ,x (,% ,x ,n)))
(defmacro &= "Shorthand for (:= x (& x n))." [x n] ~(:= ,x (,& ,x ,n)))
(defmacro |= "Shorthand for (:= x (| x n))." [x n] ~(:= ,x (,| ,x ,n)))
(defmacro ^= "Shorthand for (:= x (^ x n))." [x n] ~(:= ,x (,^ ,x ,n)))
(defmacro >>= "Shorthand for (:= x (>> x n))." [x n] ~(:= ,x (,>> ,x ,n)))
(defmacro <<= "Shorthand for (:= x (<< x n))." [x n] ~(:= ,x (,<< ,x ,n)))
(defmacro >>>= "Shorthand for (:= x (>>> x n))." [x n] ~(:= ,x (,>>> ,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]
~(def ,sym (if (= nil ,sym) ,val ,sym)))
2018-05-02 03:38:53 +00:00
2018-03-12 06:06:51 +00:00
(defmacro comment
"Ignores the body of the comment."
[])
2018-03-12 06:06:51 +00:00
(defmacro if-not
"Shorthand for (if (not ... "
[condition exp-1 exp-2]
~(if ,condition ,exp-2 ,exp-1))
(defmacro when
"Evaluates the body when the condition is true. Otherwise returns nil."
[condition & body]
~(if ,condition (do ,;body)))
(defmacro unless
"Shorthand for (when (not ... "
[condition & body]
~(if ,condition nil (do ,;body)))
(defmacro cond
"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) pairs.i
(tuple 'if pairs.i
(get pairs (+ i 1))
(aux (+ i 2))))))
(aux 0))
2018-07-04 05:28:31 +00:00
(defmacro case
"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]
2018-12-08 15:53:22 +00:00
(def atm (idempotent? dispatch))
(def sym (if atm dispatch (gensym)))
(defn aux [i]
(def restlen (- (length pairs) i))
(if (= restlen 0) nil
(if (= restlen 1) pairs.i
(tuple 'if (tuple = sym 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]
2018-06-29 03:36:31 +00:00
(if (odd? (length bindings)) (error "expected even number of bindings to let"))
(def len (length bindings))
(var i 0)
(var accum @['do])
(while (< i len)
(def {i k (+ i 1) v} bindings)
(array/push accum (tuple 'def k v))
(+= i 2))
(array/concat accum body)
(tuple/slice accum 0))
2018-02-03 22:22:04 +00:00
2018-07-10 03:29:15 +00:00
(defmacro and
"Evaluates to the last argument if all preceding elements are true, otherwise
evaluates to false."
[& forms]
(var ret true)
2018-07-10 03:29:15 +00:00
(def len (length forms))
(var i len)
(while (> i 0)
(-- i)
(:= ret (if (= ret true)
forms.i
(tuple 'if forms.i ret))))
ret)
2018-07-10 03:29:15 +00:00
(defmacro or
"Evaluates to the last argument if all preceding elements are false, otherwise
evaluates to true."
[& forms]
(var ret nil)
2018-07-10 03:29:15 +00:00
(def len (length forms))
(var i len)
(while (> i 0)
(-- i)
(def fi forms.i)
2018-12-08 15:53:22 +00:00
(:= ret (if (idempotent? fi)
(tuple 'if fi fi ret)
(do
(def $fi (gensym))
(tuple 'do (tuple 'def $fi fi)
(tuple 'if $fi $fi ret))))))
ret)
2018-07-10 03:29:15 +00:00
(defmacro loop
2018-11-19 20:33:19 +00:00
"A general purpose loop macro. This macro is similar to the Common Lisp
loop macro, although intentionally much smaller in scope.
The head of the loop should be a tuple that contains a sequence of
either bindings or conditionals. A binding is a sequence of three values
that define something to loop over. They are formatted like:\n\n
\tbinding :verb object/expression\n\n
Where binding is a binding as passed to def, :verb is one of a set of keywords,
and object is any janet expression. The available verbs are:\n\n
\t:iterate - repeatedly evaluate and bind to the expression while it is truthy.\n
\t:range - loop over a range. The object should be two element tuple with a start
and end value. The range is half open, [start, end).\n
\t:keys - Iterate over the keys in a data structure.\n
\t:in - Iterate over the values in an indexed data structure or byte sequence.\n
\t:generate - Iterate over values yielded from a fiber. Can be paired with the generator
function for the producer/consumer pattern.\n\n
loop also accepts conditionals to refine the looping further. Conditionals are of
the form:\n\n
\t:modifier argument\n\n
where :modifier is one of a set of keywords, and argument is keyword dependent.
:modifier can be one of:\n\n
\t:while expression - breaks from the loop if expression is falsey.\n
\t:let bindings - defines bindings inside the loop as passed to the let macro.\n
\t:before form - evaluates a form for a side effect before of the next inner loop.\n
\t:after form - same as :before, but the side effect happens after the next inner loop.\n
\t:repeat n - repeats the next inner loop n times.\n
\t:when condition - only evaluates the loop body when condition is true.\n\n
The loop macro always evaluates to nil."
[head & body]
2018-06-29 03:36:31 +00:00
(def len (length head))
(if (not= :tuple (type head))
(error "expected tuple for loop head"))
(defn doone
[i preds &]
2018-05-26 17:46:27 +00:00
(default preds @['and])
(if (>= i len)
(tuple/prepend body 'do)
(do
(def {i bindings
(+ i 1) verb
(+ i 2) object} head)
2018-06-29 03:36:31 +00:00
(if (keyword? bindings)
2018-11-15 23:28:55 +00:00
(case bindings
2018-05-26 17:46:27 +00:00
:while (do
(array/push preds verb)
2018-05-26 17:46:27 +00:00
(doone (+ i 2) preds))
2018-11-24 04:17:45 +00:00
:let (tuple 'let verb (doone (+ i 2) preds))
:when (tuple 'if verb (doone (+ i 2) preds))
:before (tuple 'do verb (doone (+ i 2) preds))
:after (tuple 'do (doone (+ i 2) preds) verb)
:repeat (do
(def $iter (gensym))
(def $n (gensym))
(def spreds @['and (tuple < $iter $n)])
(def sub (doone (+ i 2) spreds))
(tuple 'do
(tuple 'def $n verb)
(tuple 'var $iter 0)
(tuple 'while
(tuple/slice spreds)
(tuple := $iter (tuple + 1 $iter))
sub)))
(error (string "unexpected loop predicate: " bindings)))
2018-07-04 05:28:31 +00:00
(case verb
:iterate (do
(def $iter (gensym))
(def preds @['and (tuple ':= $iter object)])
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'var $iter nil)
(tuple 'while (tuple/slice preds)
(tuple 'def bindings $iter)
subloop)))
:range (do
2018-06-29 03:36:31 +00:00
(def [start end _inc] object)
(def inc (if _inc _inc 1))
(def endsym (gensym))
(def $iter (gensym))
(def preds @['and (tuple < $iter endsym)])
2018-05-26 17:46:27 +00:00
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'var $iter start)
(tuple 'def endsym end)
(tuple 'while (tuple/slice preds)
(tuple 'def bindings $iter)
2018-05-26 17:46:27 +00:00
subloop
(tuple ':= $iter (tuple + $iter inc)))))
:keys (do
2018-07-01 19:49:33 +00:00
(def $dict (gensym))
(def $iter (gensym))
(def preds @['and (tuple not= nil $iter)])
2018-05-26 17:46:27 +00:00
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'def $dict object)
(tuple 'var $iter (tuple next $dict nil))
(tuple 'while (tuple/slice preds)
(tuple 'def bindings $iter)
2018-05-26 17:46:27 +00:00
subloop
(tuple ':= $iter (tuple next $dict $iter)))))
:in (do
2018-07-01 19:49:33 +00:00
(def $len (gensym))
(def $i (gensym))
(def $indexed (gensym))
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)
(tuple 'while (tuple/slice preds 0)
(tuple 'def bindings (tuple get $indexed $i))
2018-05-26 17:46:27 +00:00
subloop
(tuple ':= $i (tuple + 1 $i)))))
:generate (do
(def $fiber (gensym))
(def $yieldval (gensym))
(def preds @['and
(do
(def s (gensym))
(tuple 'do
(tuple 'def s (tuple fiber/status $fiber))
(tuple 'or (tuple = s :pending) (tuple = s :new))))])
(def subloop (doone (+ i 3) preds))
(tuple 'do
(tuple 'def $fiber object)
(tuple 'var $yieldval (tuple resume $fiber))
(tuple 'while (tuple/slice preds 0)
(tuple 'def bindings $yieldval)
subloop
(tuple := $yieldval (tuple resume $fiber)))))
(error (string "unexpected loop verb: " verb)))))))
2018-12-05 16:25:36 +00:00
(doone 0 nil))
(defmacro seq
"Similar to loop, but accumulates the loop body into an array and returns that.
See loop for details."
[head & body]
2018-07-01 19:49:33 +00:00
(def $accum (gensym))
2018-12-08 15:53:22 +00:00
~(do (def ,$accum @[]) (loop ,head (array/push ,$accum (do ,;body))) ,$accum))
(defmacro generate
"Create a generator expression using the loop syntax. Returns a fiber
that yields all values inside the loop in order. See loop for details."
[head & body]
~(fiber/new (fn [&] (loop ,head (yield (do ,;body))))))
(defmacro for
"Do a c style for loop for side effects. Returns nil."
[binding start end & body]
(apply loop [tuple binding :range [tuple start end]] body))
(defmacro each
"Loop over each value in ind. Returns nil."
[binding ind & body]
(apply loop [tuple binding :in ind] body))
(defmacro coro
"A wrapper for making fibers. Same as (fiber/new (fn [&] ...body))."
[& body]
(tuple fiber/new (tuple 'fn '[&] ;body)))
(defn sum
[xs]
2018-07-10 10:51:05 +00:00
(var accum 0)
2018-07-10 03:29:15 +00:00
(loop [x :in xs] (+= accum x))
accum)
(defn product
[xs]
2018-07-10 10:51:05 +00:00
(var accum 1)
2018-07-10 03:29:15 +00:00
(loop [x :in xs] (*= accum x))
accum)
(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 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 bindings.i)
(def br (get bindings (+ 1 i)))
(if (>= i len)
tru
(do
2018-12-08 15:53:22 +00:00
(def atm (idempotent? bl))
(def sym (if atm bl (gensym)))
(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
(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]
~(if-let ,bindings (do ,;body)))
(defn comp
"Takes multiple functions and returns a function that is the composition
of those functions."
[& functions]
2018-07-04 05:28:31 +00:00
(case (length functions)
0 nil
1 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]
(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 function order.
order should take two values and return true or false (a comparison).
Returns nil if args is empty."
[order args]
(def len (length args))
(when (pos? len)
(var ret args.0)
(loop [i :range [0 len]]
(def v 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))
(defn first
"Get the first element from an indexed data structure."
[xs]
xs.0)
(defn last
"Get the last element from an indexed data structure."
[xs]
(get xs (- (length xs) 1)))
###
###
2018-03-28 20:38:05 +00:00
### Indexed Combinators
###
###
2018-05-05 18:41:47 +00:00
(def sort
"Sort an array in-place. Uses quicksort and is not a stable sort."
(do
(defn partition
[a lo hi by]
(def pivot a.hi)
(var i lo)
(loop [j :range [lo hi]]
(def aj a.j)
(when (by aj pivot)
(def ai a.i)
(:= a.i aj)
(:= a.j ai)
(++ i)))
(:= a.hi a.i)
(:= 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<)))))
2018-03-28 20:38:05 +00:00
(defn sorted
"Returns a new sorted array without modifying the old one."
[ind by]
(sort (array/slice ind) by))
2018-03-28 20:38:05 +00:00
(defn reduce
"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)))
res)
2018-03-26 17:36:58 +00:00
(defn map
"Map a function over every element in an indexed data structure and
return an array of the results."
[f & inds]
(def ninds (length inds))
(if (= 0 ninds) (error "expected at least 1 indexed collection"))
(var limit (length inds.0))
(loop [i :range [0 ninds]]
(def l (length inds.i))
(if (< l limit) (:= limit l)))
(def [i1 i2 i3 i4] inds)
(def res (array/new limit))
2018-07-04 05:28:31 +00:00
(case ninds
1 (loop [i :range [0 limit]] (:= res.i (f i1.i)))
2 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i)))
3 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i i3.i)))
4 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i i3.i i4.i)))
(loop [i :range [0 limit]]
(def args (array/new ninds))
(loop [j :range [0 ninds]] (:= args.j inds.j.i))
(:= res.i (f ;args))))
res)
(defn mapcat
"Map a function over every element in an array or tuple and
use array to concatenate the results."
[f ind]
(def res @[])
(loop [x :in ind]
(array/concat res (f x)))
res)
(defn filter
"Given a predicate, take only elements from an array or tuple for
which (pred element) is truthy. Returns a new array."
[pred ind]
(def res @[])
(loop [item :in ind]
(if (pred item)
(array/push res item)))
res)
(defn count
"Count the number of items in ind for which (pred item)
is true."
[pred ind]
(var counter 0)
(loop [item :in ind]
(if (pred item)
(++ counter)))
counter)
(defn keep
"Given a predicate, take only elements from an array or tuple for
which (pred element) is truthy. Returns a new array of truthy predicate results."
[pred ind]
(def res @[])
(loop [item :in ind]
(if-let [y (pred item)]
(array/push res y)))
res)
2018-05-18 20:24:09 +00:00
(defn range
"Create an array of values [start, end) with a given step.
With one argument returns a range [0, end). With two arguments, returns
a range [start, end). With three, returns a range with optional step size."
2018-07-11 15:57:17 +00:00
[& args]
(case (length args)
1 (do
(def [n] args)
(def arr (array/new n))
2018-07-11 15:57:17 +00:00
(loop [i :range [0 n]] (put arr i i))
arr)
2 (do
(def [n m] args)
(def arr (array/new n))
2018-07-11 15:57:17 +00:00
(loop [i :range [n m]] (put arr (- i n) i))
arr)
3 (do
(def [n m s] args)
(def arr (array/new n))
(loop [i :range [n m s]] (put arr (- i n) i))
arr)
(error "expected 1 to 3 arguments to range")))
2018-05-18 20:24:09 +00:00
(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 ind.i)
(if (pred item) (:= going false) (++ i)))
(if going nil i))
(defn find
"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
"Given a predicate, take only elements from an indexed type that satisfy
the predicate, and abort on first failure. Returns a new array."
[pred ind]
(def i (find-index pred ind))
(if i
(array/slice ind 0 i)
ind))
(defn take-while
"Same as (take-until (complement pred) ind)."
[pred ind]
(take-until (complement pred) ind))
(defn drop-until
"Given a predicate, remove elements from an indexed type that satisfy
the predicate, and abort on first failure. Returns a new tuple."
[pred ind]
(def i (find-index pred ind))
(array/slice ind i))
(defn drop-while
"Same as (drop-until (complement pred) ind)."
[pred ind]
(drop-until (complement pred) ind))
(defn juxt*
"Returns the juxtaposition of functions. In other words,
((juxt* a b c) x) evaluates to ((a x) (b x) (c x))."
[& funs]
(fn [& args]
(def ret @[])
(loop [f :in funs]
(array/push ret (f ;args)))
(tuple/slice ret 0)))
(defmacro juxt
"Macro form of juxt*. Same behavior but more efficient."
[& funs]
(def parts @['tuple])
(def $args (gensym))
(loop [f :in funs]
(array/push parts (tuple apply f $args)))
(tuple 'fn (tuple '& $args) (tuple/slice parts 0)))
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."
[x & forms]
(defn fop [last n]
(def [h t] (if (= :tuple (type n))
[tuple n.0 (array/slice n 1)]
[tuple n @[]]))
(def parts (array/concat @[h last] t))
(tuple/slice parts 0))
(reduce fop x forms))
2018-03-12 16:47:05 +00:00
(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."
[x & forms]
(defn fop [last n]
(def [h t] (if (= :tuple (type n))
[tuple n.0 (array/slice n 1)]
[tuple n @[]]))
(def parts (array/concat @[h] t @[last]))
(tuple/slice parts 0))
(reduce fop x forms))
2018-03-12 16:47:05 +00:00
2018-05-05 18:41:47 +00:00
(defn partial
"Partial function application."
[f & more]
(if (zero? (length more)) f
(fn [& r] (f ;more ;r))))
(defn every?
"Returns true if each value in is truthy, otherwise the first
falsey value."
[ind]
(var res true)
(loop [x :in ind :while res]
(if x nil (:= res x)))
res)
(defn reverse
"Reverses the order of the elements in a given array or tuple and returns a new array."
[t]
(def len (length t))
(var n (dec len))
(def reversed (array/new len))
(while (>= n 0)
(array/push reversed t.n)
(-- n))
reversed)
(defn invert
"Returns a table of where the keys of an associative data structure
are the values, and the values of the keys. If multiple keys have the same
value, one key will be ignored."
[ds]
(def ret @{})
(loop [k :keys ds]
(put ret ds.k k))
ret)
(defn zipcoll
"Creates an table or tuple from two arrays/tuples. If a third argument of
:struct is given result is struct else is table. Returns a new table."
[keys vals]
(def res @{})
(def lk (length keys))
(def lv (length vals))
(def len (if (< lk lv) lk lv))
(loop [i :range [0 len]]
(put res keys.i vals.i))
res)
(defn update
"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 coll.a-key)
(:= coll.a-key (a-function old-value ;args)))
(defn merge-into
"Merges multiple tables/structs into a table. If a key appears in more than one
collection, then later values replace any previous ones.
Returns the original table."
[tab & colls]
(loop [c :in colls
key :keys c]
(:= tab.key c.key))
tab)
(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.
Returns a new table."
[& colls]
(def container @{})
(loop [c :in colls
key :keys c]
(:= container.key c.key))
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)))
(var k (next x nil))
(while (not= nil k)
(array/push arr k)
(:= k (next x k)))
arr)
2018-03-29 01:16:12 +00:00
(defn values
"Get the values of an associative data structure."
[x]
(def arr (array/new (length x)))
(var k (next x nil))
(while (not= nil k)
(array/push arr x.k)
(:= k (next x k)))
arr)
2018-03-29 01:16:12 +00:00
(defn pairs
"Get the values of an associative data structure."
[x]
(def arr (array/new (length x)))
(var k (next x nil))
(while (not= nil k)
(array/push arr (tuple k x.k))
(:= k (next x k)))
arr)
2018-03-29 01:16:12 +00:00
2018-06-03 18:21:24 +00:00
(defn frequencies
"Get the number of occurrences of each value in a indexed structure."
[ind]
(def freqs @{})
(loop
[x :in ind]
(def n freqs.x)
(:= freqs.x (if n (+ 1 n) 1)))
freqs)
2018-06-03 18:21:24 +00:00
2018-11-08 03:52:49 +00:00
(defn interleave
"Returns an array of the first elements of each col,
then the second, etc."
[& cols]
(def res @[])
2018-11-08 03:56:26 +00:00
(def ncol (length cols))
(when (> ncol 0)
(def len (min ;(map length cols)))
(loop [i :range [0 len]
ci :range [0 ncol]]
(array/push res cols.ci.i)))
2018-11-08 03:52:49 +00:00
res)
(defn distinct
"Returns an array of the the deduplicated values in xs."
[xs]
(def ret @[])
(def seen @{})
(loop [x :in xs] (if seen.x nil (do (:= seen.x true) (array/push ret x))))
ret)
(defn flatten-into
"Takes a nested array (tree), and appends the depth first traversal of
that array to an array 'into'. Returns array into."
[into xs]
(loop [x :in xs]
(if (indexed? x)
(flatten-into into x)
(array/push into x)))
into)
(defn flatten
"Takes a nested array (tree), and returns the depth first traversal of
that array. Returns a new array."
[xs]
(flatten-into @[] xs))
(defn kvs
"Takes a table or struct and returns and array of key value pairs
like @[k v k v ...]. Returns a new array."
[dict]
(def ret (array/new (* 2 (length dict))))
(loop [k :keys dict] (array/push ret k dict.k))
ret)
(defn interpose
"Returns a sequence of the elements of ind separated by
sep. Returns a new array."
[sep ind]
(def len (length ind))
(def ret (array/new (- (* 2 len) 1)))
(if (> len 0) (:= ret.0 ind.0))
(var i 1)
(while (< i len)
(array/push ret sep ind.i)
(++ i))
ret)
2018-12-08 15:53:22 +00:00
###
###
### Pattern Matching
###
###
# Sentinel value for mismatches
(def- sentinel ~',(gensym))
(defn- match-1
[pattern expr onmatch seen]
(cond
(and (symbol? pattern) (not (keyword? pattern)))
(if (get seen pattern)
~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)
(do
(put seen pattern true)
~(if (= nil (def ,pattern ,expr)) ,sentinel ,(onmatch))))
(tuple? pattern)
(match-1 pattern.0 expr (fn []
~(if (and ,;(tuple/slice pattern 1)) ,(onmatch) ,sentinel)) seen)
2018-12-08 22:56:31 +00:00
(array? pattern)
2018-12-08 15:53:22 +00:00
(do
(def len (length pattern))
(var i -1)
(with-idemp
$arr expr
~(if (indexed? ,$arr)
,((fn aux []
(++ i)
(if (= i len)
(onmatch)
(match-1 pattern.i (tuple get $arr i) aux seen))))
,sentinel)))
2018-12-08 15:53:22 +00:00
(dictionary? pattern)
(do
(var key nil)
(with-idemp
$dict expr
2018-12-08 22:56:31 +00:00
~(if (dictionary? ,$dict)
,((fn aux []
(:= key (next pattern key))
(if (= key nil)
(onmatch)
(match-1 (get pattern key) (tuple get $dict key) aux seen))))
,sentinel)))
2018-12-08 15:53:22 +00:00
:else ~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)))
(defmacro match
"Pattern matching. Match an expression x against
any number of cases. Easy case is a pattern to match against, followed
by an expression to evaluate to if that case is matched. A pattern that is
a symbol will match anything, binding x's value to that symbol. An array
will match only if all of it's elements match the corresponding elements in
x. A table or struct will match if all values match with the corresponding
values in x. A tuple pattern will match if it's first element matches, and the following
elements are treated as predicates and are true. Any other value pattern will only
match if it is equal to x."
[x & cases]
(with-idemp $x x
(def len (length cases))
(def len-1 (dec len))
((fn aux [i]
(cond
(= i len-1) (get cases i)
(< i len-1) (do
(def $res (gensym))
~(if (= ,sentinel (def ,$res ,(match-1 cases.i $x (fn [] (get cases (inc i))) @{})))
,(aux (+ 2 i))
,$res)))) 0)))
(put _env sentinel nil)
(put _env match-1 nil)
###
###
### Documentation
###
###
(var *doc-width*
"Width in columns to print documentation."
80)
(defn doc-format
"Reformat text to wrap at a given line."
[text]
(def maxcol (- *doc-width* 8))
(var buf @" ")
(var word @"")
(var current 0)
(defn pushword
[]
(def oldcur current)
(def spacer
(if (<= maxcol (+ current (length word) 1))
(do (:= current 0) "\n ")
(do (++ current) " ")))
(+= current (length word))
(if (> oldcur 0)
(buffer/push-string buf spacer))
(buffer/push-string buf word)
(buffer/clear word))
(loop [b :in text]
(if (and (not= b 10) (not= b 32))
(if (= b 9)
(buffer/push-string word " ")
(buffer/push-byte word b))
(do
(if (> (length word) 0) (pushword))
(when (= b 10)
(buffer/push-string buf "\n ")
(:= current 0)))))
# Last word
(pushword)
buf)
(defn doc*
"Get the documentation for a symbol in a given environment."
[env sym]
(def x env.sym)
(if (not x)
(print "symbol " sym " not found.")
(do
(def d x:doc)
(print "\n\n" (if d (doc-format d) "no documentation found.") "\n\n"))))
(defmacro doc
"Shows documentation for the given symbol."
[sym]
(tuple doc* '_env (tuple 'quote sym)))
###
###
### Macro Expansion
###
###
2018-03-15 01:46:56 +00:00
(defn macex1
"Expand macros in a form, but do not recursively expand macros."
[x]
2018-11-15 23:28:55 +00:00
(defn dotable [t on-value]
(def newt @{})
(var key (next t nil))
(while (not= nil key)
(put newt (macex1 key) (on-value t.key))
(:= key (next t key)))
newt)
(defn expand-bindings [x]
2018-07-04 05:28:31 +00:00
(case (type x)
:array (map expand-bindings x)
:tuple (tuple/slice (map expand-bindings x))
:table (dotable x expand-bindings)
:struct (table/to-struct (dotable x expand-bindings))
(macex1 x)))
(defn expanddef [t]
2018-11-15 23:28:55 +00:00
(def last (get t (- (length t) 1)))
(def bound t.1)
(tuple/slice
(array/concat
@[t.0 (expand-bindings bound)]
(tuple/slice t 2 -2)
@[(macex1 last)])))
(defn expandall [t]
(def args (map macex1 (tuple/slice t 1)))
(tuple t.0 ;args))
(defn expandfn [t]
(if (symbol? t.1)
2018-11-15 23:28:55 +00:00
(do
(def args (map macex1 (tuple/slice t 3)))
(tuple 'fn t.1 t.2 ;args))
2018-11-15 23:28:55 +00:00
(do
(def args (map macex1 (tuple/slice t 2)))
(tuple 'fn t.1 ;args))))
(defn expandqq [t]
(defn qq [x]
(case (type x)
:tuple (do
(def x0 x.0)
(if (or (= 'unquote x0) (= 'unquote-splicing x0))
(tuple x0 (macex1 x.1))
(tuple/slice (map qq x))))
:array (map qq x)
:table (table (map qq (kvs x)))
:struct (struct (map qq (kvs x)))
x))
(tuple t.0 (qq t.1)))
(def specs
{':= expanddef
'def expanddef
'do expandall
'fn expandfn
'if expandall
'quote identity
'quasiquote expandqq
'var expanddef
'while expandall})
(defn dotup [t]
(def h t.0)
(def s specs.h)
(def entry (or *env*.h {}))
(def m entry:value)
(def m? entry:macro)
(cond
s (s t)
m? (m ;(tuple/slice t 1))
(tuple/slice (map macex1 t))))
(def ret
(case (type x)
:tuple (dotup x)
:array (map macex1 x)
:struct (table/to-struct (dotable x macex1))
:table (dotable x macex1)
x))
ret)
2018-03-18 18:01:58 +00:00
(defn all [pred xs]
(var ret true)
(loop [x :in xs :while ret] (:= ret (pred x)))
ret)
2018-06-29 21:42:00 +00:00
(defn some [pred xs]
(var ret nil)
(loop [x :in xs :while (not ret)] (if-let [y (pred x)] (:= ret y)))
ret)
2018-06-29 21:42:00 +00:00
(defn deep-not= [x y]
"Like not=, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than not=."
(def tx (type x))
(or
(not= tx (type y))
2018-07-04 05:28:31 +00:00
(case tx
:tuple (or (not= (length x) (length y)) (some identity (map deep-not= x y)))
:array (or (not= (length x) (length y)) (some identity (map deep-not= x y)))
:struct (deep-not= (pairs x) (pairs y))
:table (deep-not= (table/to-struct x) (table/to-struct y))
:buffer (not= (string x) (string y))
(not= x y))))
(defn deep= [x y]
"Like =, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than =."
(not (deep-not= x y)))
(defn macex
"Expand macros completely."
[x]
(var previous x)
(var current (macex1 x))
(var counter 0)
(while (deep-not= current previous)
(if (> (++ counter) 200)
(error "macro expansion too nested"))
(:= previous current)
(:= current (macex1 current)))
current)
2018-03-18 18:01:58 +00:00
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
:doc "The environment table for the current scope."})
newenv)
(defn run-context
2018-09-06 02:18:42 +00:00
"Run a context. This evaluates expressions of janet in an environment,
and is encapsulates the parsing, compilation, and evaluation of janet.
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,
2018-11-30 19:17:10 +00:00
network connection, etc. onstatus is a callback that is
invoked when a result is returned or any other signal is raised.
This function can be used to implement a repl very easily, simply
2018-11-30 19:17:10 +00:00
pass a function that reads line from stdin to chunks, status-pp to onstatus"
[env chunks onstatus where &]
# Are we done yet?
(var going true)
# The parser object
(def p (parser/new))
# Evaluate 1 source form
(defn eval1 [source]
(var good true)
(def f
(fiber/new
(fn []
(def res (compile source env where))
(if (= (type res) :function)
(res)
(do
(:= good false)
(def {:error err :start start :end end :fiber errf} res)
2018-11-30 19:17:10 +00:00
(onstatus
:compile
(if (<= 0 start)
(string err "\n at (" start ":" end ")")
err)
2018-11-30 19:17:10 +00:00
errf
where))))
:a))
2018-10-17 03:08:26 +00:00
(def res (resume f nil))
(when good
(if going (onstatus (fiber/status f) res f where))))
(def oldenv *env*)
(:= *env* env)
# Run loop
(def buf @"")
(while going
(buffer/clear buf)
(chunks buf p)
(var pindex 0)
(def len (length buf))
(if (= len 0) (:= going false))
(while (> len pindex)
(+= pindex (parser/consume p buf pindex))
(case (parser/status p)
:full (eval1 (parser/produce p))
:error (do
(def (line col) (parser/where p))
2018-11-30 19:17:10 +00:00
(onstatus :parse
(string (parser/error p)
" on line " line
2018-11-30 19:17:10 +00:00
", column " col)
nil
where)))))
(:= *env* oldenv)
env)
2018-11-30 19:17:10 +00:00
(defn status-pp
"Pretty print a signal and asscoaited state. Can be used as the
onsignal argument to run-context."
[sig x f source]
(def title
(case sig
:parse "parse error"
:compile "compile error"
:error "error"
(string "status " sig)))
(file/write stderr
2018-11-30 19:17:10 +00:00
(string title " in " source ": ")
(if (bytes? x) x (string/pretty x))
"\n")
(when f
(loop
[nf :in (reverse (debug/lineage f))
:before (file/write stderr " (fiber)\n")
{:function func
:tail tail
:pc pc
:c c
:name name
:source source
:source-start start
:source-end end} :in (debug/stack nf)]
(file/write stderr " in")
(when c (file/write stderr " cfunction"))
(if name
(file/write stderr " " name)
(when func (file/write stderr " <anonymous>")))
(if source
(do
(file/write stderr " [" source "]")
(if start
(file/write
2018-10-17 03:08:26 +00:00
stderr
" at ("
(string start)
":"
(string end)
")"))))
(if (and (not start) pc)
(file/write stderr " (pc=" (string pc) ")"))
(when tail (file/write stderr " (tailcall)"))
(file/write stderr "\n"))))
2018-12-06 01:48:29 +00:00
(defn eval-string
"Evaluates a string in the current environment. If more control over the
environment is needed, use run-context."
[str]
(var state (string str))
2018-08-03 17:41:44 +00:00
(defn chunks [buf _]
(def ret state)
(:= state nil)
(when ret
(buffer/push-string buf str)
(buffer/push-string buf "\n")))
(var returnval nil)
(run-context *env* chunks
2018-11-30 19:17:10 +00:00
(fn [sig x f source]
(if (= sig :dead)
(:= returnval x)
(status-pp sig x f source)))
"eval")
returnval)
2018-12-06 01:48:29 +00:00
(defn eval
"Evaluates a form in the current environment. If more control over the
environment is needed, use run-context."
[form]
(def res (compile form *env* "eval"))
(if (= (type res) :function)
(res)
(error res:error)))
(do
(def syspath (or (os/getenv "JANET_PATH") "/usr/local/lib/janet/"))
(defglobal 'module/paths
2018-09-06 02:18:42 +00:00
@["./?.janet"
"./?/init.janet"
"./janet_modules/?.janet"
"./janet_modules/?/init.janet"
(string syspath janet/version "/?.janet")
(string syspath janet/version "/?/init.janet")
2018-09-06 02:18:42 +00:00
(string syspath "/?.janet")
(string syspath "/?/init.janet")])
(defglobal 'module/native-paths
@["./?.so"
"./?/??.so"
2018-09-06 02:18:42 +00:00
"./janet_modules/?.so"
"./janet_modules/?/??.so"
(string syspath janet/version "/?.so")
(string syspath janet/version "/?/??.so")
(string syspath "/?.so")
(string syspath "/?/??.so")]))
(if (= :windows (os/which))
(loop [i :range [0 (length module/native-paths)]]
(def x (get module/native-paths i))
2018-08-07 04:54:47 +00:00
(put
module/native-paths
2018-08-07 04:54:47 +00:00
i
(string/replace ".so" ".dll" x))))
(defn module/find
2018-12-06 01:48:29 +00:00
"Try to match a module or path name from the patterns in paths."
2018-05-20 01:29:22 +00:00
[path paths]
(def parts (string/split "/" path))
(def lastpart (get parts (- (length parts) 1)))
(array/push
(map (fn [x]
(def y (string/replace "??" lastpart x))
(string/replace "?" path y))
2018-05-20 01:29:22 +00:00
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
returned from compiling and running the file."
(do
(defn check-mod
[f testpath]
(if f f (file/open testpath)))
(defn find-mod [path]
(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]
(def paths (module/find path module/native-paths))
(reduce check-native nil paths))
(def cache @{})
(def loading @{})
(fn require [path args &]
(when loading.path
(error (string "circular dependency: module " path " is loading")))
(def {:exit exit-on-error} (or args {}))
(def check cache.path)
(if check
check
(do
(def newenv (make-env))
(:= cache.path newenv)
(:= loading.path true)
(def f (find-mod path))
(if f
(do
2018-09-06 02:18:42 +00:00
# Normal janet module
(defn chunks [buf _] (file/read f 1024 buf))
2018-11-30 19:17:10 +00:00
(run-context newenv chunks
(fn [sig x f source]
(when (not= sig :dead)
(status-pp sig x f source)
(if exit-on-error (os/exit 1))))
path)
(file/close f))
(do
# Try native module
(def n (find-native path))
(if (not n)
(error (string "could not open file for module " path)))
((native n) newenv)))
(:= loading.path false)
newenv)))))
(defn import*
[env path & args]
(def targs (table ;args))
2018-10-17 03:08:26 +00:00
(def {:as as
:prefix prefix} 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 newenv.k)
(when (not 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
2018-08-03 17:41:44 +00:00
(defmacro import
"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."
2018-08-03 17:41:44 +00:00
[path & args]
2018-05-19 02:18:34 +00:00
(def argm (map (fn [x]
(if (keyword? x)
x
(string x)))
args))
(tuple import* '_env (string path) ;argm))
2018-08-03 17:41:44 +00:00
(defn repl
"Run a repl. The first parameter is an optional function to call to
2018-11-30 19:17:10 +00:00
get a chunk of source code that should return nil for end of file.
The second parameter is a function that is called when a signal is
2018-11-30 19:17:10 +00:00
caught."
[chunks onsignal &]
(def newenv (make-env))
(default chunks (fn [buf _] (file/read stdin :line buf)))
2018-11-30 19:17:10 +00:00
(default onsignal (fn [sig x f source]
(put newenv '_fiber @{:value f})
2018-11-30 19:17:10 +00:00
(case sig
:dead (do
(put newenv '_ @{:value x})
(print (string/pretty x 20)))
2018-11-30 19:17:10 +00:00
(status-pp sig x f source))))
(run-context newenv chunks onsignal "repl"))
(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))))
(def symbol-set @{})
(loop [envi :in envs
k :keys envi]
(:= symbol-set.k true))
(sort (keys symbol-set)))