2019-03-22 22:34:50 +00:00
|
|
|
# The core janet library
|
2020-01-29 05:38:52 +00:00
|
|
|
# Copyright 2020 © Calvin Rose
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
###
|
|
|
|
###
|
|
|
|
### Macros and Basic Functions
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(def defn :macro
|
2019-06-09 13:18:16 +00:00
|
|
|
"(defn name & more)\n\nDefine a function. Equivalent to (def name (fn name [args] ...))."
|
2019-03-22 22:34:50 +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)
|
|
|
|
(set docstr ith)
|
|
|
|
(array/push modifiers ith))
|
|
|
|
(if (< i len) (recur (+ i 1)))))))
|
|
|
|
(def start (fstart 0))
|
2019-11-08 23:35:27 +00:00
|
|
|
(def args (in more start))
|
2019-03-22 22:34:50 +00:00
|
|
|
# Add function signature to docstring
|
|
|
|
(var index 0)
|
|
|
|
(def arglen (length args))
|
|
|
|
(def buf (buffer "(" name))
|
|
|
|
(while (< index arglen)
|
|
|
|
(buffer/push-string buf " ")
|
2020-04-05 02:12:05 +00:00
|
|
|
(buffer/format buf "%j" (in args index))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set 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))
|
|
|
|
|
|
|
|
(defmacro defn-
|
|
|
|
"Define a private function that will not be exported."
|
|
|
|
[name & more]
|
|
|
|
(apply defn name :private more))
|
|
|
|
|
|
|
|
(defmacro def-
|
|
|
|
"Define a private value that will not be exported."
|
|
|
|
[name & more]
|
|
|
|
~(def ,name :private ,;more))
|
|
|
|
|
2020-01-25 04:52:28 +00:00
|
|
|
(defmacro var-
|
|
|
|
"Define a private var that will not be exported."
|
|
|
|
[name & more]
|
|
|
|
~(var ,name :private ,;more))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn defglobal
|
|
|
|
"Dynamically create a global def."
|
|
|
|
[name value]
|
|
|
|
(def name* (symbol name))
|
2019-04-16 19:41:45 +00:00
|
|
|
(setdyn name* @{:value value})
|
2019-03-22 22:34:50 +00:00
|
|
|
nil)
|
|
|
|
|
|
|
|
(defn varglobal
|
|
|
|
"Dynamically create a global var."
|
|
|
|
[name init]
|
|
|
|
(def name* (symbol name))
|
2019-04-16 19:41:45 +00:00
|
|
|
(setdyn name* @{:ref @[init]})
|
2019-03-22 22:34:50 +00:00
|
|
|
nil)
|
|
|
|
|
|
|
|
# Basic predicates
|
2019-12-04 03:24:22 +00:00
|
|
|
(defn nan? "Check if x is NaN" [x] (not= x x))
|
2020-03-28 15:23:28 +00:00
|
|
|
(defn even? "Check if x is even." [x] (= 0 (mod x 2)))
|
|
|
|
(defn odd? "Check if x is odd." [x] (= 1 (mod x 2)))
|
2019-12-28 20:57:36 +00:00
|
|
|
(defn zero? "Check if x is zero." [x] (= x 0))
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn pos? "Check if x is greater than 0." [x] (> x 0))
|
|
|
|
(defn neg? "Check if x is less than 0." [x] (< x 0))
|
2019-12-28 20:57:36 +00:00
|
|
|
(defn one? "Check if x is equal to 1." [x] (= x 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn number? "Check if x is a number." [x] (= (type x) :number))
|
|
|
|
(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." [x] (= (type x) :keyword))
|
|
|
|
(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? "Check if x a table." [x] (= (type x) :table))
|
|
|
|
(defn struct? "Check if x a struct." [x] (= (type x) :struct))
|
|
|
|
(defn array? "Check if x is an array." [x] (= (type x) :array))
|
|
|
|
(defn tuple? "Check if x is a tuple." [x] (= (type x) :tuple))
|
|
|
|
(defn boolean? "Check if x is a boolean." [x] (= (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 (if (= t :keyword) 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)))
|
2019-12-01 01:34:41 +00:00
|
|
|
(defn truthy? "Check if x is truthy." [x] (if x true false))
|
2019-03-22 22:34:50 +00:00
|
|
|
(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)))
|
2019-08-04 17:18:57 +00:00
|
|
|
|
2019-03-22 22:34:50 +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})
|
2019-11-08 23:35:27 +00:00
|
|
|
(fn idempotent? [x] (not (in non-atomic-types (type x))))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
# C style macros and functions for imperative sugar. No bitwise though.
|
|
|
|
(defn inc "Returns x + 1." [x] (+ x 1))
|
|
|
|
(defn dec "Returns x - 1." [x] (- x 1))
|
|
|
|
(defmacro ++ "Increments the var x by 1." [x] ~(set ,x (,+ ,x ,1)))
|
|
|
|
(defmacro -- "Decrements the var x by 1." [x] ~(set ,x (,- ,x ,1)))
|
|
|
|
(defmacro += "Increments the var x by n." [x n] ~(set ,x (,+ ,x ,n)))
|
|
|
|
(defmacro -= "Decrements the var x by n." [x n] ~(set ,x (,- ,x ,n)))
|
|
|
|
(defmacro *= "Shorthand for (set x (* x n))." [x n] ~(set ,x (,* ,x ,n)))
|
|
|
|
(defmacro /= "Shorthand for (set x (/ x n))." [x n] ~(set ,x (,/ ,x ,n)))
|
|
|
|
(defmacro %= "Shorthand for (set x (% x n))." [x n] ~(set ,x (,% ,x ,n)))
|
|
|
|
|
2020-01-16 04:38:06 +00:00
|
|
|
(defn assert
|
|
|
|
"Throw an error if x is not truthy."
|
|
|
|
[x &opt err]
|
|
|
|
(if x x (error (if err err "assert failure"))))
|
|
|
|
|
2020-05-09 16:06:02 +00:00
|
|
|
(defn errorf
|
|
|
|
"A combination of error and string/format. Equivalent to (error (string/format fmt ;args))"
|
|
|
|
[fmt & args]
|
|
|
|
(error (string/format fmt ;args)))
|
|
|
|
|
2019-03-22 22:34:50 +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)))
|
|
|
|
|
|
|
|
(defmacro comment
|
|
|
|
"Ignores the body of the comment."
|
2019-05-26 02:50:15 +00:00
|
|
|
[&])
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro if-not
|
2019-05-16 15:43:21 +00:00
|
|
|
"Shorthand for (if (not condition) else then)."
|
|
|
|
[condition then &opt else]
|
|
|
|
~(if ,condition ,else ,then))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro when
|
|
|
|
"Evaluates the body when the condition is true. Otherwise returns nil."
|
|
|
|
[condition & body]
|
|
|
|
~(if ,condition (do ,;body)))
|
|
|
|
|
|
|
|
(defmacro unless
|
2019-05-16 15:43:21 +00:00
|
|
|
"Shorthand for (when (not condition) ;body). "
|
2019-03-22 22:34:50 +00:00
|
|
|
[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
|
2019-11-08 23:35:27 +00:00
|
|
|
(if (= restlen 1) (in pairs i)
|
|
|
|
(tuple 'if (in pairs i)
|
|
|
|
(in pairs (+ i 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(aux (+ i 2))))))
|
|
|
|
(aux 0))
|
|
|
|
|
|
|
|
(defmacro case
|
|
|
|
"Select the body that equals the dispatch value. When pairs
|
|
|
|
has an odd number of arguments, the last is the default expression.
|
2019-05-16 15:43:21 +00:00
|
|
|
If no match is found, returns nil."
|
2019-03-22 22:34:50 +00:00
|
|
|
[dispatch & pairs]
|
|
|
|
(def atm (idempotent? dispatch))
|
|
|
|
(def sym (if atm dispatch (gensym)))
|
|
|
|
(defn aux [i]
|
|
|
|
(def restlen (- (length pairs) i))
|
|
|
|
(if (= restlen 0) nil
|
2019-11-08 23:35:27 +00:00
|
|
|
(if (= restlen 1) (in pairs i)
|
|
|
|
(tuple 'if (tuple = sym (in pairs i))
|
|
|
|
(in pairs (+ i 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(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]
|
|
|
|
(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))
|
|
|
|
|
|
|
|
(defmacro try
|
|
|
|
"Try something and catch errors. Body is any expression,
|
|
|
|
and catch should be a form with the first element a tuple. This tuple
|
|
|
|
should contain a binding for errors and an optional binding for
|
|
|
|
the fiber wrapping the body. Returns the result of body if no error,
|
|
|
|
or the result of catch if an error."
|
|
|
|
[body catch]
|
|
|
|
(let [[[err fib]] catch
|
|
|
|
f (gensym)
|
|
|
|
r (gensym)]
|
2019-04-16 19:41:45 +00:00
|
|
|
~(let [,f (,fiber/new (fn [] ,body) :ie)
|
2019-12-09 23:32:02 +00:00
|
|
|
,r (,resume ,f)]
|
2020-01-17 00:10:17 +00:00
|
|
|
(if (,= (,fiber/status ,f) :error)
|
2019-03-22 22:34:50 +00:00
|
|
|
(do (def ,err ,r) ,(if fib ~(def ,fib ,f)) ,;(tuple/slice catch 1))
|
|
|
|
,r))))
|
|
|
|
|
2019-12-09 23:32:02 +00:00
|
|
|
(defmacro protect
|
|
|
|
"Evaluate expressions, while capturing any errors. Evaluates to a tuple
|
|
|
|
of two elements. The first element is true if successful, false if an
|
|
|
|
error, and the second is the return value or error."
|
|
|
|
[& body]
|
|
|
|
(let [f (gensym) r (gensym)]
|
|
|
|
~(let [,f (,fiber/new (fn [] ,;body) :ie)
|
2020-05-05 14:17:09 +00:00
|
|
|
,r (,resume ,f)]
|
|
|
|
[(,not= :error (,fiber/status ,f)) ,r])))
|
2019-12-09 23:32:02 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defmacro and
|
2020-03-06 14:36:21 +00:00
|
|
|
"Evaluates to the last argument if all preceding elements are truthy, otherwise
|
|
|
|
evaluates to the first falsey argument."
|
2019-03-22 22:34:50 +00:00
|
|
|
[& forms]
|
|
|
|
(var ret true)
|
|
|
|
(def len (length forms))
|
|
|
|
(var i len)
|
|
|
|
(while (> i 0)
|
|
|
|
(-- i)
|
2020-03-06 14:36:21 +00:00
|
|
|
(def v (in forms i))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set ret (if (= ret true)
|
2020-03-06 14:36:21 +00:00
|
|
|
v
|
|
|
|
(if (idempotent? v)
|
|
|
|
['if v ret v]
|
|
|
|
(do (def s (gensym))
|
|
|
|
['if ['def s v] ret s])))))
|
2019-03-22 22:34:50 +00:00
|
|
|
ret)
|
|
|
|
|
|
|
|
(defmacro or
|
2020-03-06 14:36:21 +00:00
|
|
|
"Evaluates to the last argument if all preceding elements are falsey, otherwise
|
|
|
|
evaluates to the first truthy element."
|
2019-03-22 22:34:50 +00:00
|
|
|
[& forms]
|
|
|
|
(def len (length forms))
|
2020-03-06 14:36:21 +00:00
|
|
|
(var i (- len 1))
|
|
|
|
(var ret (in forms i))
|
2019-03-22 22:34:50 +00:00
|
|
|
(while (> i 0)
|
|
|
|
(-- i)
|
2019-11-08 23:35:27 +00:00
|
|
|
(def fi (in forms i))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set 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)
|
|
|
|
|
|
|
|
(defmacro with-syms
|
|
|
|
"Evaluates body with each symbol in syms bound to a generated, unique symbol."
|
|
|
|
[syms & body]
|
|
|
|
(var i 0)
|
|
|
|
(def len (length syms))
|
|
|
|
(def accum @[])
|
|
|
|
(while (< i len)
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push accum (in syms i) [gensym])
|
2019-03-22 22:34:50 +00:00
|
|
|
(++ i))
|
|
|
|
~(let (,;accum) ,;body))
|
|
|
|
|
2020-01-16 04:38:06 +00:00
|
|
|
(defmacro defer
|
2020-02-23 19:31:27 +00:00
|
|
|
"Run form unconditionally after body, even if the body throws an error.
|
|
|
|
Will also run form if a user signal 0-4 is received."
|
2020-01-16 04:38:06 +00:00
|
|
|
[form & body]
|
|
|
|
(with-syms [f r]
|
|
|
|
~(do
|
2020-02-23 19:31:27 +00:00
|
|
|
(def ,f (,fiber/new (fn [] ,;body) :ti))
|
2020-01-16 04:38:06 +00:00
|
|
|
(def ,r (,resume ,f))
|
|
|
|
,form
|
|
|
|
(if (= (,fiber/status ,f) :dead)
|
|
|
|
,r
|
2020-04-14 01:24:11 +00:00
|
|
|
(,propagate ,r ,f)))))
|
|
|
|
|
|
|
|
(defmacro edefer
|
|
|
|
"Run form after body in the case that body terminates abnormally (an error or user signal 0-4).
|
|
|
|
Otherwise, return last form in body."
|
|
|
|
[form & body]
|
|
|
|
(with-syms [f r]
|
|
|
|
~(do
|
|
|
|
(def ,f (,fiber/new (fn [] ,;body) :ti))
|
|
|
|
(def ,r (,resume ,f))
|
|
|
|
(if (= (,fiber/status ,f) :dead)
|
|
|
|
,r
|
|
|
|
(do ,form (,propagate ,r ,f))))))
|
2020-01-16 04:38:06 +00:00
|
|
|
|
2020-02-23 22:35:33 +00:00
|
|
|
(defmacro prompt
|
2020-02-23 23:15:04 +00:00
|
|
|
"Set up a checkpoint that can be returned to. Tag should be a value
|
2020-02-23 22:35:33 +00:00
|
|
|
that is used in a return statement, like a keyword."
|
|
|
|
[tag & body]
|
|
|
|
(with-syms [res target payload fib]
|
|
|
|
~(do
|
|
|
|
(def ,fib (,fiber/new (fn [] [,tag (do ,;body)]) :i0))
|
|
|
|
(def ,res (,resume ,fib))
|
|
|
|
(def [,target ,payload] ,res)
|
|
|
|
(if (,= ,tag ,target)
|
|
|
|
,payload
|
2020-04-14 01:24:11 +00:00
|
|
|
(,propagate ,res ,fib)))))
|
2020-02-23 22:35:33 +00:00
|
|
|
|
2020-03-11 03:46:18 +00:00
|
|
|
(defmacro chr
|
|
|
|
"Convert a string of length 1 to its byte (ascii) value at compile time."
|
|
|
|
[c]
|
|
|
|
(unless (and (string? c) (= (length c) 1))
|
|
|
|
(error (string/format "expected string of length 1, got %v" c)))
|
|
|
|
(c 0))
|
|
|
|
|
2020-02-23 23:15:04 +00:00
|
|
|
(defmacro label
|
|
|
|
"Set a label point that is lexically scoped. Name should be a symbol
|
|
|
|
that will be bound to the label."
|
|
|
|
[name & body]
|
|
|
|
~(do
|
2020-02-23 23:35:01 +00:00
|
|
|
(def ,name @"")
|
2020-02-23 23:15:04 +00:00
|
|
|
,(apply prompt name body)))
|
|
|
|
|
2020-02-23 22:35:33 +00:00
|
|
|
(defn return
|
|
|
|
"Return to a prompt point."
|
2020-02-23 23:15:04 +00:00
|
|
|
[to &opt value]
|
2020-02-23 22:35:33 +00:00
|
|
|
(signal 0 [to value]))
|
|
|
|
|
2019-06-25 02:02:37 +00:00
|
|
|
(defmacro with
|
2019-06-24 16:44:13 +00:00
|
|
|
"Evaluate body with some resource, which will be automatically cleaned up
|
|
|
|
if there is an error in body. binding is bound to the expression ctor, and
|
|
|
|
dtor is a function or callable that is passed the binding. If no destructor
|
|
|
|
(dtor) is given, will call :close on the resource."
|
|
|
|
[[binding ctor dtor] & body]
|
2020-01-16 04:38:06 +00:00
|
|
|
~(do
|
|
|
|
(def ,binding ,ctor)
|
2020-01-19 16:37:05 +00:00
|
|
|
,(apply defer [(or dtor :close) binding] body)))
|
2019-06-24 16:44:13 +00:00
|
|
|
|
2020-01-16 04:49:41 +00:00
|
|
|
(defmacro when-with
|
|
|
|
"Similar to with, but if binding is false or nil, returns
|
|
|
|
nil without evaluating the body. Otherwise, the same as with."
|
|
|
|
[[binding ctor dtor] & body]
|
|
|
|
~(if-let [,binding ,ctor]
|
2020-01-19 16:37:05 +00:00
|
|
|
,(apply defer [(or dtor :close) binding] body)))
|
2020-01-16 04:49:41 +00:00
|
|
|
|
|
|
|
(defmacro if-with
|
2020-01-17 00:12:05 +00:00
|
|
|
"Similar to with, but if binding is false or nil, evaluates
|
|
|
|
the falsey path. Otherwise, evaluates the truthy path. In both cases,
|
|
|
|
ctor is bound to binding."
|
2020-05-05 14:17:09 +00:00
|
|
|
[[binding ctor dtor] truthy &opt falsey]
|
2020-01-16 04:49:41 +00:00
|
|
|
~(if-let [,binding ,ctor]
|
2020-01-19 16:37:05 +00:00
|
|
|
,(apply defer [(or dtor :close) binding] [truthy])
|
2020-01-16 04:49:41 +00:00
|
|
|
,falsey))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn- for-template
|
|
|
|
[binding start stop step comparison delta body]
|
|
|
|
(with-syms [i s]
|
|
|
|
~(do
|
|
|
|
(var ,i ,start)
|
|
|
|
(def ,s ,stop)
|
|
|
|
(while (,comparison ,i ,s)
|
|
|
|
(def ,binding ,i)
|
|
|
|
,;body
|
|
|
|
(set ,i (,delta ,i ,step))))))
|
|
|
|
|
2020-03-07 15:31:52 +00:00
|
|
|
(defn- check-indexed [x]
|
|
|
|
(if (indexed? x)
|
|
|
|
x
|
|
|
|
(error (string "expected tuple for range, got " x))))
|
|
|
|
|
|
|
|
(defn- range-template
|
|
|
|
[binding object rest op comparison]
|
|
|
|
(let [[start stop step] (check-indexed object)]
|
|
|
|
(for-template binding start stop (or step 1) comparison op [rest])))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn- each-template
|
2020-01-18 23:55:07 +00:00
|
|
|
[binding inx body]
|
|
|
|
(with-syms [k]
|
|
|
|
(def ds (if (idempotent? inx) inx (gensym)))
|
2019-03-22 22:34:50 +00:00
|
|
|
~(do
|
2020-01-18 23:55:07 +00:00
|
|
|
,(unless (= ds inx) ~(def ,ds ,inx))
|
|
|
|
(var ,k (,next ,ds nil))
|
|
|
|
(while (,not= nil ,k)
|
|
|
|
(def ,binding (,in ,ds ,k))
|
2019-03-22 22:34:50 +00:00
|
|
|
,;body
|
2020-01-18 23:55:07 +00:00
|
|
|
(set ,k (,next ,ds ,k))))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn- keys-template
|
|
|
|
[binding in pair? body]
|
|
|
|
(with-syms [k]
|
|
|
|
(def ds (if (idempotent? in) in (gensym)))
|
|
|
|
~(do
|
|
|
|
,(unless (= ds in) ~(def ,ds ,in))
|
|
|
|
(var ,k (,next ,ds nil))
|
2020-01-18 23:55:07 +00:00
|
|
|
(while (,not= nil ,k)
|
2019-11-08 23:35:27 +00:00
|
|
|
(def ,binding ,(if pair? ~(tuple ,k (in ,ds ,k)) k))
|
2019-03-22 22:34:50 +00:00
|
|
|
,;body
|
|
|
|
(set ,k (,next ,ds ,k))))))
|
|
|
|
|
|
|
|
(defn- iterate-template
|
|
|
|
[binding expr body]
|
|
|
|
(with-syms [i]
|
|
|
|
~(do
|
|
|
|
(var ,i nil)
|
|
|
|
(while (set ,i ,expr)
|
2019-04-28 04:34:32 +00:00
|
|
|
(def ,binding ,i)
|
2019-03-22 22:34:50 +00:00
|
|
|
,body))))
|
|
|
|
|
|
|
|
(defn- loop1
|
|
|
|
[body head i]
|
|
|
|
|
2019-11-08 23:35:27 +00:00
|
|
|
# Terminate recursion
|
|
|
|
(when (<= (length head) i)
|
|
|
|
(break ~(do ,;body)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-11-08 23:35:27 +00:00
|
|
|
(def {i binding
|
|
|
|
(+ i 1) verb} head)
|
|
|
|
|
|
|
|
# 2 term expression
|
|
|
|
(when (keyword? binding)
|
|
|
|
(break
|
|
|
|
(let [rest (loop1 body head (+ i 2))]
|
|
|
|
(case binding
|
|
|
|
:until ~(do (if ,verb (break) nil) ,rest)
|
|
|
|
:while ~(do (if ,verb nil (break)) ,rest)
|
|
|
|
:let ~(let ,verb (do ,rest))
|
|
|
|
:after ~(do ,rest ,verb nil)
|
|
|
|
:before ~(do ,verb ,rest nil)
|
|
|
|
:repeat (with-syms [iter]
|
|
|
|
~(do (var ,iter ,verb) (while (> ,iter 0) ,rest (-- ,iter))))
|
|
|
|
:when ~(when ,verb ,rest)
|
|
|
|
(error (string "unexpected loop modifier " binding))))))
|
|
|
|
|
|
|
|
# 3 term expression
|
|
|
|
(def {(+ i 2) object} head)
|
|
|
|
(let [rest (loop1 body head (+ i 3))]
|
|
|
|
(case verb
|
2020-03-07 15:31:52 +00:00
|
|
|
:range (range-template binding object rest + <)
|
|
|
|
:range-to (range-template binding object rest + <=)
|
|
|
|
:down (range-template binding object rest - >)
|
|
|
|
:down-to (range-template binding object rest - >=)
|
2019-11-08 23:35:27 +00:00
|
|
|
:keys (keys-template binding object false [rest])
|
|
|
|
:pairs (keys-template binding object true [rest])
|
|
|
|
:in (each-template binding object [rest])
|
|
|
|
:iterate (iterate-template binding object rest)
|
|
|
|
:generate (with-syms [f s]
|
|
|
|
~(let [,f ,object]
|
|
|
|
(while true
|
|
|
|
(def ,binding (,resume ,f))
|
|
|
|
(if (= :dead (,fiber/status ,f)) (break))
|
|
|
|
,rest)))
|
|
|
|
(error (string "unexpected loop verb " verb)))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro for
|
|
|
|
"Do a c style for loop for side effects. Returns nil."
|
|
|
|
[i start stop & body]
|
|
|
|
(for-template i start stop 1 < + body))
|
|
|
|
|
2020-01-19 00:44:41 +00:00
|
|
|
(defmacro eachk
|
2020-04-10 16:36:23 +00:00
|
|
|
"Loop over each key in ds. Returns nil."
|
2020-01-19 00:44:41 +00:00
|
|
|
[x ds & body]
|
|
|
|
(keys-template x ds false body))
|
|
|
|
|
|
|
|
(defmacro eachp
|
|
|
|
"Loop over each (key, value) pair in ds. Returns nil."
|
|
|
|
[x ds & body]
|
|
|
|
(keys-template x ds true body))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defmacro each
|
2020-01-19 00:44:41 +00:00
|
|
|
"Loop over each value in ds. Returns nil."
|
|
|
|
[x ds & body]
|
|
|
|
(each-template x ds body))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro loop
|
|
|
|
"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,
|
2020-04-16 17:11:17 +00:00
|
|
|
and object is any expression. The available verbs are:\n\n
|
2019-03-22 22:34:50 +00:00
|
|
|
\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
|
2019-08-04 17:18:57 +00:00
|
|
|
and end value, and an optional positive step. The range is half open, [start, end).\n
|
2020-03-07 15:31:52 +00:00
|
|
|
\t:range-to - same as :range, but the range is inclusive [start, end].\n
|
|
|
|
\t:down - loop over a range, stepping downwards. The object should be two element tuple
|
|
|
|
with a start and (exclusive) end value, and an optional (positive!) step size.\n
|
|
|
|
\t:down-to - same :as down, but the range is inclusive [start, end].\n
|
|
|
|
\t:keys - iterate over the keys in a data structure.\n
|
|
|
|
\t:pairs - iterate over the keys value pairs as tuples in a data structure.\n
|
|
|
|
\t:in - iterate over the values in a data structure.\n
|
|
|
|
\t:generate - iterate over values yielded from a fiber. Can be paired with the generator
|
2019-03-22 22:34:50 +00:00
|
|
|
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
|
2019-06-18 02:46:38 +00:00
|
|
|
\t:until expression - breaks from the loop if expression is truthy.\n
|
2019-03-22 22:34:50 +00:00
|
|
|
\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]
|
|
|
|
(loop1 body head 0))
|
|
|
|
|
|
|
|
(put _env 'loop1 nil)
|
2019-12-04 14:18:54 +00:00
|
|
|
(put _env 'check-indexed nil)
|
2019-03-22 22:34:50 +00:00
|
|
|
(put _env 'for-template nil)
|
|
|
|
(put _env 'iterate-template nil)
|
|
|
|
(put _env 'each-template nil)
|
|
|
|
(put _env 'keys-template nil)
|
2020-03-07 15:31:52 +00:00
|
|
|
(put _env 'range-template nil)
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro seq
|
|
|
|
"Similar to loop, but accumulates the loop body into an array and returns that.
|
|
|
|
See loop for details."
|
|
|
|
[head & body]
|
|
|
|
(def $accum (gensym))
|
|
|
|
~(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]
|
2019-04-16 19:41:45 +00:00
|
|
|
~(fiber/new (fn [] (loop ,head (yield (do ,;body)))) :yi))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro coro
|
2019-05-16 15:43:21 +00:00
|
|
|
"A wrapper for making fibers. Same as (fiber/new (fn [] ;body) :yi)."
|
2019-03-22 22:34:50 +00:00
|
|
|
[& body]
|
2019-04-16 19:41:45 +00:00
|
|
|
(tuple fiber/new (tuple 'fn '[] ;body) :yi))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn sum
|
|
|
|
"Returns the sum of xs. If xs is empty, returns 0."
|
|
|
|
[xs]
|
|
|
|
(var accum 0)
|
|
|
|
(each x xs (+= accum x))
|
|
|
|
accum)
|
|
|
|
|
2019-09-20 01:26:16 +00:00
|
|
|
(defn mean
|
|
|
|
"Returns the mean of xs. If empty, returns NaN."
|
|
|
|
[xs]
|
|
|
|
(/ (sum xs) (length xs)))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn product
|
|
|
|
"Returns the product of xs. If xs is empty, returns 1."
|
|
|
|
[xs]
|
|
|
|
(var accum 1)
|
|
|
|
(each x xs (*= accum x))
|
|
|
|
accum)
|
|
|
|
|
|
|
|
(defmacro if-let
|
|
|
|
"Make multiple bindings, and if all are truthy,
|
|
|
|
evaluate the tru form. If any are false or nil, evaluate
|
|
|
|
the fal form. Bindings have the same syntax as the let macro."
|
|
|
|
[bindings tru &opt 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]
|
|
|
|
(if (>= i len)
|
|
|
|
tru
|
|
|
|
(do
|
2019-11-08 23:35:27 +00:00
|
|
|
(def bl (in bindings i))
|
|
|
|
(def br (in bindings (+ 1 i)))
|
2019-03-22 22:34:50 +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
|
|
|
|
"Same as (if-let bindings (do ;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]
|
|
|
|
(case (length functions)
|
|
|
|
0 nil
|
2019-11-08 23:35:27 +00:00
|
|
|
1 (in functions 0)
|
2020-05-05 14:17:09 +00:00
|
|
|
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))))))
|
2019-08-03 19:53:14 +00:00
|
|
|
(let [[f g h i] functions]
|
|
|
|
(comp (fn [x] (f (g (h (i x)))))
|
|
|
|
;(tuple/slice functions 4 -1)))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(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]
|
|
|
|
(var [ret] args)
|
|
|
|
(each x args (if (order x ret) (set ret x)))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn max
|
|
|
|
"Returns the numeric maximum of the arguments."
|
|
|
|
[& args] (extreme > args))
|
|
|
|
|
|
|
|
(defn min
|
|
|
|
"Returns the numeric minimum of the arguments."
|
|
|
|
[& args] (extreme < args))
|
|
|
|
|
|
|
|
(defn first
|
|
|
|
"Get the first element from an indexed data structure."
|
|
|
|
[xs]
|
2020-02-02 05:35:17 +00:00
|
|
|
(get xs 0))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn last
|
|
|
|
"Get the last element from an indexed data structure."
|
|
|
|
[xs]
|
2020-04-09 12:35:57 +00:00
|
|
|
(get xs (- (length xs) 1)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
###
|
|
|
|
###
|
|
|
|
### Indexed Combinators
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2020-04-10 01:42:49 +00:00
|
|
|
(defn- sort-part
|
|
|
|
[a lo hi by]
|
|
|
|
(def pivot (in a hi))
|
|
|
|
(var i lo)
|
|
|
|
(for j lo hi
|
|
|
|
(def aj (in a j))
|
|
|
|
(when (by aj pivot)
|
|
|
|
(def ai (in a i))
|
|
|
|
(set (a i) aj)
|
|
|
|
(set (a j) ai)
|
|
|
|
(++ i)))
|
|
|
|
(set (a hi) (in a i))
|
|
|
|
(set (a i) pivot)
|
|
|
|
i)
|
|
|
|
|
|
|
|
(defn- sort-help
|
|
|
|
[a lo hi by]
|
|
|
|
(when (> hi lo)
|
|
|
|
(def piv (sort-part a lo hi by))
|
|
|
|
(sort-help a lo (- piv 1) by)
|
|
|
|
(sort-help a (+ piv 1) hi by))
|
|
|
|
a)
|
|
|
|
|
|
|
|
(defn sort
|
|
|
|
"Sort an array in-place. Uses quick-sort and is not a stable sort."
|
|
|
|
[a &opt by]
|
|
|
|
(sort-help a 0 (- (length a) 1) (or by <)))
|
|
|
|
|
|
|
|
(put _env 'sort-part nil)
|
|
|
|
(put _env 'sort-help nil)
|
|
|
|
|
|
|
|
(defn sort-by
|
|
|
|
"Returns a new sorted array that compares elements by invoking
|
|
|
|
a function on each element and comparing the result with <."
|
|
|
|
[f ind]
|
|
|
|
(sort ind (fn [x y] (< (f x) (f y)))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn sorted
|
|
|
|
"Returns a new sorted array without modifying the old one."
|
2019-12-28 20:57:36 +00:00
|
|
|
[ind &opt by]
|
2019-03-22 22:34:50 +00:00
|
|
|
(sort (array/slice ind) by))
|
|
|
|
|
2020-04-10 01:42:49 +00:00
|
|
|
(defn sorted-by
|
|
|
|
"Returns a new sorted array that compares elements by invoking
|
|
|
|
a function on each element and comparing the result with <."
|
|
|
|
[f ind]
|
|
|
|
(sorted ind (fn [x y] (< (f x) (f y)))))
|
|
|
|
|
2019-03-22 22:34:50 +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)
|
|
|
|
(each x ind (set res (f res x)))
|
|
|
|
res)
|
|
|
|
|
2020-03-27 02:35:11 +00:00
|
|
|
(defn reduce2
|
|
|
|
"The 2 argument version of reduce that does not take an initialization value.
|
|
|
|
Instead the first element of the array is used for initialization."
|
|
|
|
[f ind]
|
|
|
|
(var k (next ind))
|
2020-03-27 17:45:40 +00:00
|
|
|
(if (= nil k) (break nil))
|
2020-03-27 02:35:11 +00:00
|
|
|
(var res (in ind k))
|
|
|
|
(set k (next ind k))
|
|
|
|
(while (not= nil k)
|
|
|
|
(set res (f res (in ind k)))
|
|
|
|
(set k (next ind k)))
|
|
|
|
res)
|
|
|
|
|
|
|
|
(defn accumulate
|
|
|
|
"Similar to reduce, but accumulates intermediate values into an array.
|
|
|
|
The last element in the array is what would be the return value from reduce.
|
|
|
|
The init value is not added to the array.
|
|
|
|
Returns a new array."
|
|
|
|
[f init ind]
|
|
|
|
(var res init)
|
|
|
|
(def ret (array/new (length ind)))
|
|
|
|
(each x ind (array/push ret (set res (f res x))))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn accumulate2
|
|
|
|
"The 2 argument version of accumulate that does not take an initialization value."
|
|
|
|
[f ind]
|
|
|
|
(var k (next ind))
|
|
|
|
(def ret (array/new (length ind)))
|
2020-03-27 17:45:40 +00:00
|
|
|
(if (= nil k) (break ret))
|
2020-03-27 02:35:11 +00:00
|
|
|
(var res (in ind k))
|
|
|
|
(array/push ret res)
|
|
|
|
(set k (next ind k))
|
|
|
|
(while (not= nil k)
|
|
|
|
(set res (f res (in ind k)))
|
|
|
|
(array/push ret res)
|
|
|
|
(set k (next ind k)))
|
|
|
|
ret)
|
|
|
|
|
2019-03-22 22:34:50 +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"))
|
2019-11-08 23:35:27 +00:00
|
|
|
(var limit (length (in inds 0)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(for i 0 ninds
|
2019-11-08 23:35:27 +00:00
|
|
|
(def l (length (in inds i)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (< l limit) (set limit l)))
|
|
|
|
(def [i1 i2 i3 i4] inds)
|
|
|
|
(def res (array/new limit))
|
|
|
|
(case ninds
|
2019-11-08 23:35:27 +00:00
|
|
|
1 (for i 0 limit (set (res i) (f (in i1 i))))
|
|
|
|
2 (for i 0 limit (set (res i) (f (in i1 i) (in i2 i))))
|
|
|
|
3 (for i 0 limit (set (res i) (f (in i1 i) (in i2 i) (in i3 i))))
|
|
|
|
4 (for i 0 limit (set (res i) (f (in i1 i) (in i2 i) (in i3 i) (in i4 i))))
|
2019-03-22 22:34:50 +00:00
|
|
|
(for i 0 limit
|
|
|
|
(def args (array/new ninds))
|
2019-11-08 23:35:27 +00:00
|
|
|
(for j 0 ninds (set (args j) (in (in inds j) i)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set (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 @[])
|
|
|
|
(each x 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 @[])
|
|
|
|
(each item 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)
|
|
|
|
(each item 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 @[])
|
|
|
|
(each item ind
|
|
|
|
(if-let [y (pred item)]
|
|
|
|
(array/push res y)))
|
|
|
|
res)
|
|
|
|
|
|
|
|
(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."
|
|
|
|
[& args]
|
|
|
|
(case (length args)
|
|
|
|
1 (do
|
|
|
|
(def [n] args)
|
|
|
|
(def arr (array/new n))
|
|
|
|
(for i 0 n (put arr i i))
|
|
|
|
arr)
|
|
|
|
2 (do
|
|
|
|
(def [n m] args)
|
|
|
|
(def arr (array/new (- m n)))
|
|
|
|
(for i n m (put arr (- i n) i))
|
|
|
|
arr)
|
|
|
|
3 (do
|
|
|
|
(def [n m s] args)
|
2020-05-15 22:19:37 +00:00
|
|
|
(cond
|
|
|
|
(zero? s) @[]
|
|
|
|
(neg? s) (seq [i :down [n m (- s)]] i)
|
2019-03-22 22:34:50 +00:00
|
|
|
(seq [i :range [n m s]] i)))
|
|
|
|
(error "expected 1 to 3 arguments to range")))
|
|
|
|
|
|
|
|
(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)
|
2019-11-08 23:35:27 +00:00
|
|
|
(def item (in ind i))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (pred item) (set going false) (++ i)))
|
|
|
|
(if going nil i))
|
|
|
|
|
|
|
|
(defn find
|
|
|
|
"Find the first value in an indexed collection that satisfies a predicate. Returns
|
2019-05-16 15:43:21 +00:00
|
|
|
nil if not found. Note there is no way to differentiate a nil from the indexed collection
|
2019-03-22 22:34:50 +00:00
|
|
|
and a not found. Consider find-index if this is an issue."
|
|
|
|
[pred ind]
|
|
|
|
(def i (find-index pred ind))
|
2019-11-08 23:35:27 +00:00
|
|
|
(if (= i nil) nil (in ind i)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-08-05 13:36:24 +00:00
|
|
|
(defn take
|
|
|
|
"Take first n elements in an indexed type. Returns new indexed instance."
|
2019-08-06 07:33:55 +00:00
|
|
|
[n ind]
|
|
|
|
(def use-str (bytes? ind))
|
2019-08-06 06:19:22 +00:00
|
|
|
(def f (if use-str string/slice tuple/slice))
|
2020-01-24 23:35:21 +00:00
|
|
|
(def len (length ind))
|
2019-08-06 06:25:09 +00:00
|
|
|
# make sure end is in [0, len]
|
2020-01-24 23:39:25 +00:00
|
|
|
(def m (if (> n 0) n 0))
|
|
|
|
(def end (if (> m len) len m))
|
2019-08-06 07:33:55 +00:00
|
|
|
(f ind 0 end))
|
2019-08-05 13:36:24 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn take-until
|
2019-08-05 11:58:51 +00:00
|
|
|
"Same as (take-while (complement pred) ind)."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred ind]
|
2019-08-06 07:33:55 +00:00
|
|
|
(def use-str (bytes? ind))
|
|
|
|
(def f (if use-str string/slice tuple/slice))
|
|
|
|
(def len (length ind))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def i (find-index pred ind))
|
2019-08-06 07:33:55 +00:00
|
|
|
(def end (if (nil? i) len i))
|
|
|
|
(f ind 0 end))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn take-while
|
2019-08-05 11:58:51 +00:00
|
|
|
"Given a predicate, take only elements from an indexed type that satisfy
|
|
|
|
the predicate, and abort on first failure. Returns a new array."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred ind]
|
|
|
|
(take-until (complement pred) ind))
|
|
|
|
|
2019-08-05 13:36:24 +00:00
|
|
|
(defn drop
|
|
|
|
"Drop first n elements in an indexed type. Returns new indexed instance."
|
2019-08-06 07:33:55 +00:00
|
|
|
[n ind]
|
|
|
|
(def use-str (bytes? ind))
|
2019-08-06 06:19:22 +00:00
|
|
|
(def f (if use-str string/slice tuple/slice))
|
2020-01-24 23:35:21 +00:00
|
|
|
(def len (length ind))
|
2019-08-06 06:25:09 +00:00
|
|
|
# make sure start is in [0, len]
|
2020-01-24 23:39:25 +00:00
|
|
|
(def m (if (> n 0) n 0))
|
|
|
|
(def start (if (> m len) len m))
|
2019-08-06 07:33:55 +00:00
|
|
|
(f ind start -1))
|
2019-08-05 13:36:24 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn drop-until
|
2019-08-05 11:58:51 +00:00
|
|
|
"Same as (drop-while (complement pred) ind)."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred ind]
|
2019-08-06 07:33:55 +00:00
|
|
|
(def use-str (bytes? ind))
|
|
|
|
(def f (if use-str string/slice tuple/slice))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def i (find-index pred ind))
|
2019-08-06 07:33:55 +00:00
|
|
|
(def len (length ind))
|
|
|
|
(def start (if (nil? i) len i))
|
|
|
|
(f ind start))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn drop-while
|
2019-08-05 11:58:51 +00:00
|
|
|
"Given a predicate, remove elements from an indexed type that satisfy
|
|
|
|
the predicate, and abort on first failure. Returns a new array."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred ind]
|
|
|
|
(drop-until (complement pred) ind))
|
|
|
|
|
|
|
|
(defn juxt*
|
|
|
|
"Returns the juxtaposition of functions. In other words,
|
2019-04-28 04:34:32 +00:00
|
|
|
((juxt* a b c) x) evaluates to [(a x) (b x) (c x)]."
|
2019-03-22 22:34:50 +00:00
|
|
|
[& funs]
|
|
|
|
(fn [& args]
|
|
|
|
(def ret @[])
|
|
|
|
(each f 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))
|
|
|
|
(each f funs
|
|
|
|
(array/push parts (tuple apply f $args)))
|
|
|
|
(tuple 'fn (tuple '& $args) (tuple/slice parts 0)))
|
|
|
|
|
2020-05-18 20:55:21 +00:00
|
|
|
(defmacro tracev
|
2020-05-18 22:43:41 +00:00
|
|
|
"Print a value and a description of the form that produced that value to
|
|
|
|
stderr. Evaluates to x."
|
|
|
|
[x]
|
2020-05-18 23:22:43 +00:00
|
|
|
(def [l c] (tuple/sourcemap (dyn :macro-form ())))
|
|
|
|
(def cf (dyn :current-file))
|
|
|
|
(def fmt-1 (if cf (string/format "trace [%s]" cf) "trace"))
|
2020-05-19 00:05:27 +00:00
|
|
|
(def fmt-2 (if (or (neg? l) (neg? c)) ":" (string/format " on line %d, column %d:" l c)))
|
2020-05-18 23:22:43 +00:00
|
|
|
(def fmt (string fmt-1 fmt-2 " %j is "))
|
2020-05-18 22:43:41 +00:00
|
|
|
(def s (gensym))
|
|
|
|
~(let [,s ,x]
|
2020-05-18 23:22:43 +00:00
|
|
|
(,eprinf ,fmt ',x)
|
2020-05-18 22:43:41 +00:00
|
|
|
(,eprintf (,dyn :pretty-format "%q") ,s)
|
|
|
|
,s))
|
2020-05-16 14:15:57 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defmacro ->
|
|
|
|
"Threading macro. Inserts x as the second value in the first form
|
|
|
|
in forms, and inserts the modified first 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))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in n 0) (array/slice n 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple n @[])))
|
|
|
|
(def parts (array/concat @[h last] t))
|
|
|
|
(tuple/slice parts 0))
|
|
|
|
(reduce fop x forms))
|
|
|
|
|
|
|
|
(defmacro ->>
|
|
|
|
"Threading macro. Inserts x as the last value in the first form
|
|
|
|
in forms, and inserts the modified first 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))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in n 0) (array/slice n 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple n @[])))
|
|
|
|
(def parts (array/concat @[h] t @[last]))
|
|
|
|
(tuple/slice parts 0))
|
|
|
|
(reduce fop x forms))
|
|
|
|
|
|
|
|
(defmacro -?>
|
2020-04-01 03:32:17 +00:00
|
|
|
"Short circuit threading macro. Inserts x as the second value in the first form
|
2019-03-22 22:34:50 +00:00
|
|
|
in forms, and inserts the modified first form into the second form
|
|
|
|
in the same manner, and so on. The pipeline will return nil
|
|
|
|
if an intermediate value is nil.
|
|
|
|
Useful for expressing pipelines of data."
|
|
|
|
[x & forms]
|
|
|
|
(defn fop [last n]
|
|
|
|
(def [h t] (if (= :tuple (type n))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in n 0) (array/slice n 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple n @[])))
|
|
|
|
(def sym (gensym))
|
|
|
|
(def parts (array/concat @[h sym] t))
|
|
|
|
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
|
|
|
|
(reduce fop x forms))
|
|
|
|
|
|
|
|
(defmacro -?>>
|
2020-04-01 03:32:17 +00:00
|
|
|
"Short circuit threading macro. Inserts x as the last value in the first form
|
2019-03-22 22:34:50 +00:00
|
|
|
in forms, and inserts the modified first form into the second form
|
|
|
|
in the same manner, and so on. The pipeline will return nil
|
|
|
|
if an intermediate value is nil.
|
|
|
|
Useful for expressing pipelines of data."
|
|
|
|
[x & forms]
|
|
|
|
(defn fop [last n]
|
|
|
|
(def [h t] (if (= :tuple (type n))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in n 0) (array/slice n 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple n @[])))
|
|
|
|
(def sym (gensym))
|
|
|
|
(def parts (array/concat @[h] t @[sym]))
|
|
|
|
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
|
|
|
|
(reduce fop x forms))
|
|
|
|
|
|
|
|
(defn walk-ind [f form]
|
|
|
|
(def len (length form))
|
|
|
|
(def ret (array/new len))
|
|
|
|
(each x form (array/push ret (f x)))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn walk-dict [f form]
|
|
|
|
(def ret @{})
|
|
|
|
(loop [k :keys form]
|
2019-11-08 23:35:27 +00:00
|
|
|
(put ret (f k) (f (in form k))))
|
2019-03-22 22:34:50 +00:00
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn walk
|
|
|
|
"Iterate over the values in ast and apply f
|
2019-11-17 02:43:21 +00:00
|
|
|
to them. Collect the results in a data structure. If ast is not a
|
2019-03-22 22:34:50 +00:00
|
|
|
table, struct, array, or tuple,
|
|
|
|
returns form."
|
|
|
|
[f form]
|
|
|
|
(case (type form)
|
|
|
|
:table (walk-dict f form)
|
|
|
|
:struct (table/to-struct (walk-dict f form))
|
|
|
|
:array (walk-ind f form)
|
|
|
|
:tuple (tuple/slice (walk-ind f form))
|
|
|
|
form))
|
|
|
|
|
|
|
|
(put _env 'walk-ind nil)
|
|
|
|
(put _env 'walk-dict nil)
|
|
|
|
|
|
|
|
(defn postwalk
|
|
|
|
"Do a post-order traversal of a data structure and call (f x)
|
|
|
|
on every visitation."
|
|
|
|
[f form]
|
|
|
|
(f (walk (fn [x] (postwalk f x)) form)))
|
|
|
|
|
|
|
|
(defn prewalk
|
|
|
|
"Similar to postwalk, but do pre-order traversal."
|
|
|
|
[f form]
|
|
|
|
(walk (fn [x] (prewalk f x)) (f form)))
|
|
|
|
|
|
|
|
(defmacro as->
|
|
|
|
"Thread forms together, replacing as in forms with the value
|
|
|
|
of the previous form. The first for is the value x. Returns the
|
|
|
|
last value."
|
|
|
|
[x as & forms]
|
|
|
|
(var prev x)
|
|
|
|
(each form forms
|
|
|
|
(def sym (gensym))
|
|
|
|
(def next-prev (postwalk (fn [y] (if (= y as) sym y)) form))
|
|
|
|
(set prev ~(let [,sym ,prev] ,next-prev)))
|
|
|
|
prev)
|
|
|
|
|
|
|
|
(defmacro as?->
|
|
|
|
"Thread forms together, replacing as in forms with the value
|
|
|
|
of the previous form. The first for is the value x. If any
|
|
|
|
intermediate values are falsey, return nil; otherwise, returns the
|
|
|
|
last value."
|
|
|
|
[x as & forms]
|
|
|
|
(var prev x)
|
|
|
|
(each form forms
|
|
|
|
(def sym (gensym))
|
|
|
|
(def next-prev (postwalk (fn [y] (if (= y as) sym y)) form))
|
|
|
|
(set prev ~(if-let [,sym ,prev] ,next-prev)))
|
|
|
|
prev)
|
|
|
|
|
2019-04-16 23:10:01 +00:00
|
|
|
(defmacro with-dyns
|
|
|
|
"Run a block of code in a new fiber that has some
|
|
|
|
dynamic bindings set. The fiber will not mask errors
|
|
|
|
or signals, but the dynamic bindings will be properly
|
|
|
|
unset, as dynamic bindings are fiber local."
|
|
|
|
[bindings & body]
|
2019-06-08 14:30:30 +00:00
|
|
|
(def dyn-forms
|
|
|
|
(seq [i :range [0 (length bindings) 2]]
|
2020-05-05 14:17:09 +00:00
|
|
|
~(setdyn ,(bindings i) ,(bindings (+ i 1)))))
|
2019-06-08 14:30:30 +00:00
|
|
|
~(,resume (,fiber/new (fn [] ,;dyn-forms ,;body) :p)))
|
2019-04-16 23:10:01 +00:00
|
|
|
|
2019-11-01 02:58:43 +00:00
|
|
|
(defmacro with-vars
|
|
|
|
"Evaluates body with each var in vars temporarily bound. Similar signature to
|
|
|
|
let, but each binding must be a var."
|
|
|
|
[vars & body]
|
|
|
|
(def len (length vars))
|
|
|
|
(unless (even? len) (error "expected even number of argument to vars"))
|
|
|
|
(def temp (seq [i :range [0 len 2]] (gensym)))
|
|
|
|
(def saveold (seq [i :range [0 len 2]] ['def (temp (/ i 2)) (vars i)]))
|
|
|
|
(def setnew (seq [i :range [0 len 2]] ['set (vars i) (vars (+ i 1))]))
|
|
|
|
(def restoreold (seq [i :range [0 len 2]] ['set (vars i) (temp (/ i 2))]))
|
|
|
|
(with-syms [ret f s]
|
2020-05-05 14:17:09 +00:00
|
|
|
~(do
|
|
|
|
,;saveold
|
|
|
|
(def ,f (,fiber/new (fn [] ,;setnew ,;body) :ti))
|
|
|
|
(def ,ret (,resume ,f))
|
|
|
|
,;restoreold
|
|
|
|
(if (= (,fiber/status ,f) :dead) ,ret (,propagate ,ret ,f)))))
|
2019-11-01 02:58:43 +00:00
|
|
|
|
2019-03-22 22:34:50 +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 (set 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))
|
2019-11-01 02:58:43 +00:00
|
|
|
(var n (- len 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def reversed (array/new len))
|
|
|
|
(while (>= n 0)
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push reversed (in t n))
|
2019-03-22 22:34:50 +00:00
|
|
|
(-- 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]
|
2019-11-08 23:35:27 +00:00
|
|
|
(put ret (in ds k) k))
|
2019-03-22 22:34:50 +00:00
|
|
|
ret)
|
2019-03-22 22:07:10 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn zipcoll
|
|
|
|
"Creates a table from two arrays/tuples.
|
|
|
|
Returns a new table."
|
2019-11-16 23:17:13 +00:00
|
|
|
[ks vs]
|
2019-03-22 22:34:50 +00:00
|
|
|
(def res @{})
|
2019-11-16 23:17:13 +00:00
|
|
|
(def lk (length ks))
|
|
|
|
(def lv (length vs))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def len (if (< lk lv) lk lv))
|
|
|
|
(for i 0 len
|
2019-11-16 23:17:13 +00:00
|
|
|
(put res (in ks i) (in vs i)))
|
2019-03-22 22:34:50 +00:00
|
|
|
res)
|
2019-03-22 22:07:10 +00:00
|
|
|
|
2019-09-05 18:11:53 +00:00
|
|
|
(defn get-in
|
|
|
|
"Access a value in a nested data structure. Looks into the data structure via
|
|
|
|
a sequence of keys."
|
|
|
|
[ds ks &opt dflt]
|
|
|
|
(var d ds)
|
|
|
|
(loop [k :in ks :while d] (set d (get d k)))
|
2019-11-16 23:17:13 +00:00
|
|
|
(if (= nil d) dflt d))
|
2019-09-05 18:11:53 +00:00
|
|
|
|
|
|
|
(defn update-in
|
2019-09-05 18:19:25 +00:00
|
|
|
"Update a value in a nested data structure by applying f to the current value.
|
|
|
|
Looks into the data structure via
|
2019-09-05 18:11:53 +00:00
|
|
|
a sequence of keys. Missing data structures will be replaced with tables. Returns
|
|
|
|
the modified, original data structure."
|
|
|
|
[ds ks f & args]
|
|
|
|
(var d ds)
|
2019-09-05 18:19:25 +00:00
|
|
|
(def len-1 (- (length ks) 1))
|
|
|
|
(if (< len-1 0) (error "expected at least 1 key in ks"))
|
|
|
|
(for i 0 len-1
|
2019-09-05 18:11:53 +00:00
|
|
|
(def k (get ks i))
|
|
|
|
(def v (get d k))
|
|
|
|
(if (= nil v)
|
|
|
|
(let [newv (table)]
|
|
|
|
(put d k newv)
|
|
|
|
(set d newv))
|
|
|
|
(set d v)))
|
2019-09-05 18:19:25 +00:00
|
|
|
(def last-key (get ks len-1))
|
2019-09-05 18:11:53 +00:00
|
|
|
(def last-val (get d last-key))
|
|
|
|
(put d last-key (f last-val ;args))
|
|
|
|
ds)
|
|
|
|
|
2019-09-05 18:19:25 +00:00
|
|
|
(defn put-in
|
|
|
|
"Put a value into a nested data structure.
|
|
|
|
Looks into the data structure via
|
|
|
|
a sequence of keys. Missing data structures will be replaced with tables. Returns
|
|
|
|
the modified, original data structure."
|
|
|
|
[ds ks v]
|
|
|
|
(var d ds)
|
|
|
|
(def len-1 (- (length ks) 1))
|
|
|
|
(if (< len-1 0) (error "expected at least 1 key in ks"))
|
|
|
|
(for i 0 len-1
|
|
|
|
(def k (get ks i))
|
|
|
|
(def v (get d k))
|
|
|
|
(if (= nil v)
|
|
|
|
(let [newv (table)]
|
|
|
|
(put d k newv)
|
|
|
|
(set d newv))
|
|
|
|
(set d v)))
|
|
|
|
(def last-key (get ks len-1))
|
|
|
|
(def last-val (get d last-key))
|
|
|
|
(put d last-key v)
|
|
|
|
ds)
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn update
|
2019-04-21 19:44:03 +00:00
|
|
|
"Accepts a key argument and passes its associated value to a function.
|
|
|
|
The key is the re-associated to the function's return value. Returns the updated
|
|
|
|
data structure ds."
|
2019-03-22 22:34:50 +00:00
|
|
|
[ds key func & args]
|
|
|
|
(def old (get ds key))
|
2019-11-16 04:49:18 +00:00
|
|
|
(put ds key (func old ;args)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(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]
|
2019-11-08 23:35:27 +00:00
|
|
|
(set (tab key) (in c key)))
|
2019-03-22 22:34:50 +00:00
|
|
|
tab)
|
|
|
|
|
|
|
|
(defn merge
|
|
|
|
"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]
|
2019-11-08 23:35:27 +00:00
|
|
|
(set (container key) (in c key)))
|
2019-03-22 22:34:50 +00:00
|
|
|
container)
|
|
|
|
|
|
|
|
(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)
|
|
|
|
(set k (next x k)))
|
|
|
|
arr)
|
|
|
|
|
|
|
|
(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)
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push arr (in x k))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set k (next x k)))
|
|
|
|
arr)
|
|
|
|
|
|
|
|
(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)
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push arr (tuple k (in x k)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set k (next x k)))
|
|
|
|
arr)
|
|
|
|
|
|
|
|
(defn frequencies
|
|
|
|
"Get the number of occurrences of each value in a indexed structure."
|
|
|
|
[ind]
|
|
|
|
(def freqs @{})
|
|
|
|
(each x ind
|
2019-11-08 23:35:27 +00:00
|
|
|
(def n (in freqs x))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set (freqs x) (if n (+ 1 n) 1)))
|
|
|
|
freqs)
|
|
|
|
|
|
|
|
(defn interleave
|
|
|
|
"Returns an array of the first elements of each col,
|
|
|
|
then the second, etc."
|
|
|
|
[& cols]
|
|
|
|
(def res @[])
|
|
|
|
(def ncol (length cols))
|
|
|
|
(when (> ncol 0)
|
|
|
|
(def len (min ;(map length cols)))
|
|
|
|
(loop [i :range [0 len]
|
|
|
|
ci :range [0 ncol]]
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push res (in (in cols ci) i))))
|
2019-03-22 22:34:50 +00:00
|
|
|
res)
|
|
|
|
|
|
|
|
(defn distinct
|
|
|
|
"Returns an array of the deduplicated values in xs."
|
|
|
|
[xs]
|
|
|
|
(def ret @[])
|
|
|
|
(def seen @{})
|
2019-11-08 23:35:27 +00:00
|
|
|
(each x xs (if (in seen x) nil (do (put seen x true) (array/push ret x))))
|
2019-03-22 22:34:50 +00:00
|
|
|
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]
|
|
|
|
(each x 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))))
|
2019-11-08 23:35:27 +00:00
|
|
|
(loop [k :keys dict] (array/push ret k (in dict k)))
|
2019-03-22 22:34:50 +00:00
|
|
|
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)))
|
2019-11-08 23:35:27 +00:00
|
|
|
(if (> len 0) (put ret 0 (in ind 0)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(var i 1)
|
|
|
|
(while (< i len)
|
2019-11-08 23:35:27 +00:00
|
|
|
(array/push ret sep (in ind i))
|
2019-03-22 22:34:50 +00:00
|
|
|
(++ i))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn partition
|
|
|
|
"Partition an indexed data structure into tuples
|
|
|
|
of size n. Returns a new array."
|
|
|
|
[n ind]
|
|
|
|
(var i 0) (var nextn n)
|
|
|
|
(def len (length ind))
|
|
|
|
(def ret (array/new (math/ceil (/ len n))))
|
2019-05-15 04:04:25 +00:00
|
|
|
(def slicer (if (bytes? ind) string/slice tuple/slice))
|
2019-03-22 22:34:50 +00:00
|
|
|
(while (<= nextn len)
|
2019-05-15 04:04:25 +00:00
|
|
|
(array/push ret (slicer ind i nextn))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set i nextn)
|
|
|
|
(+= nextn n))
|
2019-05-15 04:04:25 +00:00
|
|
|
(if (not= i len) (array/push ret (slicer ind i)))
|
2019-03-22 22:34:50 +00:00
|
|
|
ret)
|
|
|
|
|
|
|
|
###
|
|
|
|
###
|
|
|
|
### IO Helpers
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(defn slurp
|
|
|
|
"Read all data from a file with name path
|
|
|
|
and then close the file."
|
|
|
|
[path]
|
2019-07-12 13:14:37 +00:00
|
|
|
(def f (file/open path :rb))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if-not f (error (string "could not open file " path)))
|
|
|
|
(def contents (file/read f :all))
|
|
|
|
(file/close f)
|
|
|
|
contents)
|
|
|
|
|
|
|
|
(defn spit
|
|
|
|
"Write contents to a file at path.
|
|
|
|
Can optionally append to the file."
|
|
|
|
[path contents &opt mode]
|
2019-07-12 13:14:37 +00:00
|
|
|
(default mode :wb)
|
2019-03-22 22:34:50 +00:00
|
|
|
(def f (file/open path mode))
|
|
|
|
(if-not f (error (string "could not open file " path " with mode " mode)))
|
|
|
|
(file/write f contents)
|
|
|
|
(file/close f)
|
|
|
|
nil)
|
|
|
|
|
2019-06-01 14:38:28 +00:00
|
|
|
(defn pp
|
2020-03-06 23:12:06 +00:00
|
|
|
`Pretty print to stdout or (dyn :out). The format string used is (dyn :pretty-format "%q").`
|
2019-06-01 14:38:28 +00:00
|
|
|
[x]
|
2020-02-04 00:14:32 +00:00
|
|
|
(printf (dyn :pretty-format "%q") x)
|
|
|
|
(flush))
|
2019-06-01 14:38:28 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Pattern Matching
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(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 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))))
|
|
|
|
|
|
|
|
|
|
|
|
# Sentinel value for mismatches
|
|
|
|
(def- sentinel ~',(gensym))
|
|
|
|
|
|
|
|
(defn- match-1
|
|
|
|
[pattern expr onmatch seen]
|
|
|
|
(cond
|
|
|
|
|
2020-03-07 15:38:37 +00:00
|
|
|
(= '_ pattern)
|
|
|
|
(onmatch)
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(symbol? pattern)
|
2019-11-08 23:35:27 +00:00
|
|
|
(if (in seen pattern)
|
2019-03-22 22:34:50 +00:00
|
|
|
~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)
|
|
|
|
(do
|
|
|
|
(put seen pattern true)
|
2020-03-05 15:32:43 +00:00
|
|
|
~(do (def ,pattern ,expr) ,(onmatch))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-06-10 18:00:51 +00:00
|
|
|
(and (tuple? pattern) (= :parens (tuple/type pattern)))
|
2020-05-04 23:28:20 +00:00
|
|
|
(if (= (get pattern 0) '@)
|
2019-05-16 14:05:54 +00:00
|
|
|
# Unification with external values
|
2020-05-04 23:28:20 +00:00
|
|
|
~(if (= ,(get pattern 1) ,expr) ,(onmatch) ,sentinel)
|
2019-05-16 14:05:54 +00:00
|
|
|
(match-1
|
2019-11-08 23:35:27 +00:00
|
|
|
(in pattern 0) expr
|
2019-05-16 14:05:54 +00:00
|
|
|
(fn []
|
|
|
|
~(if (and ,;(tuple/slice pattern 1)) ,(onmatch) ,sentinel)) seen))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-06-10 18:00:51 +00:00
|
|
|
(indexed? pattern)
|
2019-03-22 22:34:50 +00:00
|
|
|
(do
|
|
|
|
(def len (length pattern))
|
|
|
|
(var i -1)
|
|
|
|
(with-idemp
|
|
|
|
$arr expr
|
2020-03-05 15:32:43 +00:00
|
|
|
~(if (,indexed? ,$arr)
|
|
|
|
(if (< (,length ,$arr) ,len)
|
|
|
|
,sentinel
|
|
|
|
,((fn aux []
|
|
|
|
(++ i)
|
|
|
|
(if (= i len)
|
|
|
|
(onmatch)
|
|
|
|
(match-1 (in pattern i) (tuple in $arr i) aux seen)))))
|
2019-03-22 22:34:50 +00:00
|
|
|
,sentinel)))
|
|
|
|
|
|
|
|
(dictionary? pattern)
|
|
|
|
(do
|
|
|
|
(var key nil)
|
|
|
|
(with-idemp
|
|
|
|
$dict expr
|
2020-03-05 15:32:43 +00:00
|
|
|
~(if (,dictionary? ,$dict)
|
2019-03-22 22:34:50 +00:00
|
|
|
,((fn aux []
|
|
|
|
(set key (next pattern key))
|
2020-03-07 16:13:10 +00:00
|
|
|
(def $val (gensym))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (= key nil)
|
|
|
|
(onmatch)
|
2020-03-07 16:13:10 +00:00
|
|
|
~(do (def ,$val (,get ,$dict ,key))
|
2020-05-05 14:17:09 +00:00
|
|
|
,(match-1 [(in pattern key) [not= nil $val]] $val aux seen)))))
|
2019-03-22 22:34:50 +00:00
|
|
|
,sentinel)))
|
|
|
|
|
|
|
|
:else ~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)))
|
|
|
|
|
|
|
|
(defmacro match
|
|
|
|
"Pattern matching. Match an expression x against
|
2020-03-07 15:38:37 +00:00
|
|
|
any number of cases. Each case is a pattern to match against, followed
|
2019-03-22 22:34:50 +00:00
|
|
|
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
|
2020-03-07 15:38:37 +00:00
|
|
|
elements are treated as predicates and are true. The last special case is
|
|
|
|
the '_ symbol, which is a wildcard that will match any value without creating a binding.
|
|
|
|
Any other value pattern will only match if it is equal to x."
|
2019-03-22 22:34:50 +00:00
|
|
|
[x & cases]
|
|
|
|
(with-idemp $x x
|
|
|
|
(def len (length cases))
|
|
|
|
(def len-1 (dec len))
|
|
|
|
((fn aux [i]
|
|
|
|
(cond
|
2019-11-08 23:35:27 +00:00
|
|
|
(= i len-1) (in cases i)
|
2019-03-22 22:34:50 +00:00
|
|
|
(< i len-1) (with-syms [$res]
|
2019-11-08 23:35:27 +00:00
|
|
|
~(if (= ,sentinel (def ,$res ,(match-1 (in cases i) $x (fn [] (in cases (inc i))) @{})))
|
2019-03-22 22:34:50 +00:00
|
|
|
,(aux (+ 2 i))
|
|
|
|
,$res)))) 0)))
|
|
|
|
|
|
|
|
(put _env 'sentinel nil)
|
|
|
|
(put _env 'match-1 nil)
|
|
|
|
(put _env 'with-idemp nil)
|
|
|
|
|
|
|
|
###
|
|
|
|
###
|
|
|
|
### Documentation
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
(defn- env-walk
|
2020-04-16 17:11:17 +00:00
|
|
|
[pred &opt env local]
|
2019-11-24 23:45:53 +00:00
|
|
|
(default env (fiber/getenv (fiber/current)))
|
|
|
|
(def envs @[])
|
2020-04-16 17:11:17 +00:00
|
|
|
(do (var e env) (while e (array/push envs e) (set e (table/getproto e)) (if local (break))))
|
2019-11-24 23:45:53 +00:00
|
|
|
(def ret-set @{})
|
|
|
|
(loop [envi :in envs
|
|
|
|
k :keys envi
|
|
|
|
:when (pred k)]
|
|
|
|
(put ret-set k true))
|
|
|
|
(sort (keys ret-set)))
|
|
|
|
|
|
|
|
(defn all-bindings
|
2020-04-16 17:11:17 +00:00
|
|
|
"Get all symbols available in an environment. Defaults to the current
|
|
|
|
fiber's environment. If local is truthy, will not show inherited bindings
|
|
|
|
(from prototype tables)."
|
|
|
|
[&opt env local]
|
|
|
|
(env-walk symbol? env local))
|
2019-11-24 23:45:53 +00:00
|
|
|
|
|
|
|
(defn all-dynamics
|
|
|
|
"Get all dynamic bindings in an environment. Defaults to the current
|
2020-04-16 17:11:17 +00:00
|
|
|
fiber's environment. If local is truthy, will not show inherited bindings
|
|
|
|
(from prototype tables)."
|
|
|
|
[&opt env local]
|
|
|
|
(env-walk keyword? env local))
|
2019-11-24 23:45:53 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn doc-format
|
|
|
|
"Reformat text to wrap at a given line."
|
2020-04-10 16:36:23 +00:00
|
|
|
[text &opt width]
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2020-04-10 16:36:23 +00:00
|
|
|
(def maxcol (- (or width (dyn :doc-width 80)) 8))
|
2019-03-22 22:34:50 +00:00
|
|
|
(var buf @" ")
|
|
|
|
(var word @"")
|
|
|
|
(var current 0)
|
|
|
|
|
|
|
|
(defn pushword
|
|
|
|
[]
|
|
|
|
(def oldcur current)
|
|
|
|
(def spacer
|
|
|
|
(if (<= maxcol (+ current (length word) 1))
|
|
|
|
(do (set current 0) "\n ")
|
|
|
|
(do (++ current) " ")))
|
|
|
|
(+= current (length word))
|
|
|
|
(if (> oldcur 0)
|
|
|
|
(buffer/push-string buf spacer))
|
|
|
|
(buffer/push-string buf word)
|
|
|
|
(buffer/clear word))
|
|
|
|
|
|
|
|
(each b 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 ")
|
|
|
|
(set current 0)))))
|
|
|
|
|
|
|
|
# Last word
|
|
|
|
(pushword)
|
|
|
|
|
|
|
|
buf)
|
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
(defn- print-index
|
|
|
|
"Print bindings in the current environment given a filter function"
|
|
|
|
[fltr]
|
|
|
|
(def bindings (filter fltr (all-bindings)))
|
|
|
|
(def dynamics (map describe (filter fltr (all-dynamics))))
|
|
|
|
(print)
|
|
|
|
(print (doc-format (string "Bindings:\n\n" (string/join bindings " "))))
|
|
|
|
(print)
|
|
|
|
(print (doc-format (string "Dynamics:\n\n" (string/join dynamics " "))))
|
|
|
|
(print))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn doc*
|
2020-04-23 17:15:12 +00:00
|
|
|
"Get the documentation for a symbol in a given environment. Function form of doc."
|
2019-11-24 23:45:53 +00:00
|
|
|
[&opt sym]
|
|
|
|
|
|
|
|
(cond
|
|
|
|
(string? sym)
|
|
|
|
(print-index (fn [x] (string/find sym x)))
|
|
|
|
|
|
|
|
sym
|
2019-03-22 22:34:50 +00:00
|
|
|
(do
|
2019-11-24 23:45:53 +00:00
|
|
|
(def x (dyn sym))
|
|
|
|
(if (not x)
|
|
|
|
(print "symbol " sym " not found.")
|
|
|
|
(do
|
|
|
|
(def bind-type
|
|
|
|
(string " "
|
|
|
|
(cond
|
|
|
|
(x :ref) (string :var " (" (type (in (x :ref) 0)) ")")
|
|
|
|
(x :macro) :macro
|
|
|
|
(type (x :value)))
|
|
|
|
"\n"))
|
|
|
|
(def sm (x :source-map))
|
|
|
|
(def d (x :doc))
|
|
|
|
(print "\n\n"
|
|
|
|
(if d bind-type "")
|
|
|
|
(if-let [[path line col] sm]
|
|
|
|
(string " " path " on line " line ", column " col "\n") "")
|
|
|
|
(if (or d sm) "\n" "")
|
2019-12-28 20:57:36 +00:00
|
|
|
(if d (doc-format d) " no documentation found.")
|
2019-11-24 23:45:53 +00:00
|
|
|
"\n\n"))))
|
|
|
|
|
|
|
|
# else
|
|
|
|
(print-index identity)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defmacro doc
|
2020-04-23 17:15:12 +00:00
|
|
|
"Shows documentation for the given symbol, or can show a list of available bindings.
|
|
|
|
If sym is a symbol, will look for documentation for that symbol. If sym is a string
|
|
|
|
or is not provided, will show all lexical and dynamic bindings in the current environment with
|
|
|
|
that prefix (all bindings will be shown if no prefix is given)."
|
2019-11-24 23:45:53 +00:00
|
|
|
[&opt sym]
|
2019-04-16 19:41:45 +00:00
|
|
|
~(,doc* ',sym))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
(put _env 'env-walk nil)
|
|
|
|
(put _env 'print-index nil)
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Macro Expansion
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(defn macex1
|
2019-08-04 17:18:57 +00:00
|
|
|
"Expand macros in a form, but do not recursively expand macros.
|
|
|
|
See macex docs for info on on-binding."
|
|
|
|
[x &opt on-binding]
|
|
|
|
|
|
|
|
(when on-binding
|
|
|
|
(when (symbol? x)
|
|
|
|
(break (on-binding x))))
|
|
|
|
|
|
|
|
(defn recur [y] (macex1 y on-binding))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn dotable [t on-value]
|
|
|
|
(def newt @{})
|
|
|
|
(var key (next t nil))
|
|
|
|
(while (not= nil key)
|
2019-11-08 23:35:27 +00:00
|
|
|
(put newt (recur key) (on-value (in t key)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(set key (next t key)))
|
|
|
|
newt)
|
|
|
|
|
|
|
|
(defn expand-bindings [x]
|
|
|
|
(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))
|
2019-08-04 17:18:57 +00:00
|
|
|
(recur x)))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn expanddef [t]
|
2019-11-08 23:35:27 +00:00
|
|
|
(def last (in t (- (length t) 1)))
|
|
|
|
(def bound (in t 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple/slice
|
|
|
|
(array/concat
|
2019-11-08 23:35:27 +00:00
|
|
|
@[(in t 0) (expand-bindings bound)]
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple/slice t 2 -2)
|
2019-08-04 17:18:57 +00:00
|
|
|
@[(recur last)])))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn expandall [t]
|
2019-08-04 17:18:57 +00:00
|
|
|
(def args (map recur (tuple/slice t 1)))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in t 0) ;args))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn expandfn [t]
|
2019-11-08 23:35:27 +00:00
|
|
|
(def t1 (in t 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (symbol? t1)
|
|
|
|
(do
|
2019-08-04 17:18:57 +00:00
|
|
|
(def args (map recur (tuple/slice t 3)))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple 'fn t1 (in t 2) ;args))
|
2019-03-22 22:34:50 +00:00
|
|
|
(do
|
2019-08-04 17:18:57 +00:00
|
|
|
(def args (map recur (tuple/slice t 2)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple 'fn t1 ;args))))
|
|
|
|
|
|
|
|
(defn expandqq [t]
|
|
|
|
(defn qq [x]
|
|
|
|
(case (type x)
|
|
|
|
:tuple (do
|
2019-11-08 23:35:27 +00:00
|
|
|
(def x0 (in x 0))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (or (= 'unquote x0) (= 'unquote-splicing x0))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple x0 (recur (in x 1)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(tuple/slice (map qq x))))
|
|
|
|
:array (map qq x)
|
|
|
|
:table (table (map qq (kvs x)))
|
|
|
|
:struct (struct (map qq (kvs x)))
|
|
|
|
x))
|
2019-11-08 23:35:27 +00:00
|
|
|
(tuple (in t 0) (qq (in t 1))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(def specs
|
|
|
|
{'set expanddef
|
|
|
|
'def expanddef
|
|
|
|
'do expandall
|
|
|
|
'fn expandfn
|
|
|
|
'if expandall
|
|
|
|
'quote identity
|
|
|
|
'quasiquote expandqq
|
|
|
|
'var expanddef
|
2019-12-29 21:44:53 +00:00
|
|
|
'while expandall
|
|
|
|
'break expandall})
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn dotup [t]
|
2019-11-08 23:35:27 +00:00
|
|
|
(def h (in t 0))
|
|
|
|
(def s (in specs h))
|
2019-04-16 19:41:45 +00:00
|
|
|
(def entry (or (dyn h) {}))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def m (entry :value))
|
|
|
|
(def m? (entry :macro))
|
|
|
|
(cond
|
|
|
|
s (s t)
|
2020-05-18 23:37:41 +00:00
|
|
|
m? (do (setdyn :macro-form t) (m ;(tuple/slice t 1)))
|
2019-08-04 17:18:57 +00:00
|
|
|
(tuple/slice (map recur t))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(def ret
|
|
|
|
(case (type x)
|
|
|
|
:tuple (if (= (tuple/type x) :brackets)
|
2019-08-04 17:18:57 +00:00
|
|
|
(tuple/brackets ;(map recur x))
|
2019-03-22 22:34:50 +00:00
|
|
|
(dotup x))
|
2019-08-04 17:18:57 +00:00
|
|
|
:array (map recur x)
|
|
|
|
:struct (table/to-struct (dotable x recur))
|
|
|
|
:table (dotable x recur)
|
2019-03-22 22:34:50 +00:00
|
|
|
x))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn all
|
2020-04-16 17:11:17 +00:00
|
|
|
"Returns true if all xs are truthy, otherwise the result of first
|
2020-02-06 03:06:39 +00:00
|
|
|
falsey predicate value, (pred x)."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred xs]
|
|
|
|
(var ret true)
|
|
|
|
(loop [x :in xs :while ret] (set ret (pred x)))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn some
|
2020-02-06 03:06:39 +00:00
|
|
|
"Returns nil if all xs are false or nil, otherwise returns the result of the
|
|
|
|
first truthy predicate, (pred x)."
|
2019-03-22 22:34:50 +00:00
|
|
|
[pred xs]
|
|
|
|
(var ret nil)
|
|
|
|
(loop [x :in xs :while (not ret)] (if-let [y (pred x)] (set ret y)))
|
|
|
|
ret)
|
|
|
|
|
|
|
|
(defn deep-not=
|
|
|
|
"Like not=, but mutable types (arrays, tables, buffers) are considered
|
|
|
|
equal if they have identical structure. Much slower than not=."
|
|
|
|
[x y]
|
|
|
|
(def tx (type x))
|
|
|
|
(or
|
|
|
|
(not= tx (type y))
|
|
|
|
(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=
|
|
|
|
"Like =, but mutable types (arrays, tables, buffers) are considered
|
|
|
|
equal if they have identical structure. Much slower than =."
|
|
|
|
[x y]
|
|
|
|
(not (deep-not= x y)))
|
|
|
|
|
2019-09-05 18:11:53 +00:00
|
|
|
(defn freeze
|
|
|
|
"Freeze an object (make it immutable) and do a deep copy, making
|
|
|
|
child values also immutable. Closures, fibers, and abstract types
|
|
|
|
will not be recursively frozen, but all other types will."
|
|
|
|
[x]
|
|
|
|
(case (type x)
|
|
|
|
:array (tuple/slice (map freeze x))
|
|
|
|
:tuple (tuple/slice (map freeze x))
|
|
|
|
:table (if-let [p (table/getproto x)]
|
2020-05-05 14:17:09 +00:00
|
|
|
(freeze (merge (table/clone p) x))
|
|
|
|
(struct ;(map freeze (kvs x))))
|
2019-09-05 18:11:53 +00:00
|
|
|
:struct (struct ;(map freeze (kvs x)))
|
|
|
|
:buffer (string x)
|
|
|
|
x))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn macex
|
2019-08-04 17:18:57 +00:00
|
|
|
"Expand macros completely.
|
|
|
|
on-binding is an optional callback whenever a normal symbolic binding
|
|
|
|
is encounter. This allows macros to easily see all bindings use by their
|
|
|
|
arguments by calling macex on their contents. The binding itself is also
|
|
|
|
replaced by the value returned by on-binding within the expand macro."
|
|
|
|
[x &opt on-binding]
|
2019-03-22 22:34:50 +00:00
|
|
|
(var previous x)
|
2019-08-04 17:18:57 +00:00
|
|
|
(var current (macex1 x on-binding))
|
2019-03-22 22:34:50 +00:00
|
|
|
(var counter 0)
|
|
|
|
(while (deep-not= current previous)
|
|
|
|
(if (> (++ counter) 200)
|
|
|
|
(error "macro expansion too nested"))
|
|
|
|
(set previous current)
|
2019-08-04 17:18:57 +00:00
|
|
|
(set current (macex1 current on-binding)))
|
2019-03-22 22:34:50 +00:00
|
|
|
current)
|
|
|
|
|
2019-10-04 01:20:42 +00:00
|
|
|
(defmacro varfn
|
|
|
|
"Create a function that can be rebound. varfn has the same signature
|
|
|
|
as defn, but defines functions in the environment as vars. If a var 'name'
|
|
|
|
already exists in the environment, it is rebound to the new function. Returns
|
|
|
|
a function."
|
|
|
|
[name & body]
|
|
|
|
(def expansion (apply defn name body))
|
|
|
|
(def fbody (last expansion))
|
|
|
|
(def modifiers (tuple/slice expansion 2 -2))
|
|
|
|
(def metadata @{})
|
|
|
|
(each m modifiers
|
|
|
|
(cond
|
|
|
|
(keyword? m) (put metadata m true)
|
|
|
|
(string? m) (put metadata :doc m)
|
|
|
|
(error (string "invalid metadata " m))))
|
|
|
|
(with-syms [entry old-entry f]
|
|
|
|
~(let [,old-entry (,dyn ',name)]
|
|
|
|
(def ,entry (or ,old-entry @{:ref @[nil]}))
|
|
|
|
(,setdyn ',name ,entry)
|
|
|
|
(def ,f ,fbody)
|
|
|
|
(,put-in ,entry [:ref 0] ,f)
|
|
|
|
(,merge-into ,entry ',metadata)
|
|
|
|
,f)))
|
|
|
|
|
2019-08-04 17:18:57 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Function shorthand
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(defmacro short-fn
|
|
|
|
"fn shorthand.\n\n
|
|
|
|
usage:\n\n
|
|
|
|
\t(short-fn (+ $ $)) - A function that double's its arguments.\n
|
|
|
|
\t(short-fn (string $0 $1)) - accepting multiple args\n
|
|
|
|
\t|(+ $ $) - use pipe reader macro for terse function literals\n
|
|
|
|
\t|(+ $&) - variadic functions"
|
|
|
|
[arg]
|
|
|
|
(var max-param-seen -1)
|
|
|
|
(var vararg false)
|
|
|
|
(defn saw-special-arg
|
|
|
|
[num]
|
|
|
|
(set max-param-seen (max max-param-seen num)))
|
|
|
|
(defn on-binding
|
|
|
|
[x]
|
|
|
|
(if (string/has-prefix? '$ x)
|
|
|
|
(cond
|
|
|
|
(= '$ x)
|
|
|
|
(do
|
|
|
|
(saw-special-arg 0)
|
|
|
|
'$0)
|
|
|
|
(= '$& x)
|
|
|
|
(do
|
|
|
|
(set vararg true)
|
|
|
|
x)
|
|
|
|
:else
|
|
|
|
(do
|
|
|
|
(def num (scan-number (string/slice x 1)))
|
|
|
|
(if (nat? num)
|
|
|
|
(saw-special-arg num))
|
|
|
|
x))
|
|
|
|
x))
|
|
|
|
(def expanded (macex arg on-binding))
|
|
|
|
(def fn-args (seq [i :range [0 (+ 1 max-param-seen)]] (symbol '$ i)))
|
|
|
|
~(fn [,;fn-args ,;(if vararg ['& '$&] [])] ,expanded))
|
|
|
|
|
2019-12-15 02:39:14 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Default PEG patterns
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(def default-peg-grammar
|
|
|
|
"The default grammar used for pegs. This grammar defines several common patterns
|
|
|
|
that should make it easier to write more complex patterns."
|
|
|
|
~@{:d (range "09")
|
|
|
|
:a (range "az" "AZ")
|
|
|
|
:s (set " \t\r\n\0\f")
|
|
|
|
:w (range "az" "AZ" "09")
|
|
|
|
:S (if-not :s 1)
|
|
|
|
:W (if-not :w 1)
|
|
|
|
:A (if-not :a 1)
|
|
|
|
:D (if-not :d 1)
|
|
|
|
:d+ (some :d)
|
|
|
|
:a+ (some :a)
|
|
|
|
:s+ (some :s)
|
|
|
|
:w+ (some :w)
|
|
|
|
:d* (any :d)
|
|
|
|
:a* (any :a)
|
|
|
|
:w* (any :w)
|
|
|
|
:s* (any :s)})
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Evaluation and Compilation
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2019-07-20 21:57:07 +00:00
|
|
|
# Get boot options
|
|
|
|
(def- boot/opts @{})
|
|
|
|
(each [k v] (partition 2 (tuple/slice boot/args 2))
|
|
|
|
(put boot/opts k v))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn make-env
|
|
|
|
"Create a new environment table. The new environment
|
|
|
|
will inherit bindings from the parent environment, but new
|
|
|
|
bindings will not pollute the parent environment."
|
|
|
|
[&opt parent]
|
|
|
|
(def parent (if parent parent _env))
|
|
|
|
(def newenv (table/setproto @{} parent))
|
|
|
|
newenv)
|
|
|
|
|
|
|
|
(defn bad-parse
|
|
|
|
"Default handler for a parse error."
|
|
|
|
[p where]
|
2019-09-22 22:18:28 +00:00
|
|
|
(def ec (dyn :err-color))
|
|
|
|
(def [line col] (parser/where p))
|
2019-10-19 14:44:27 +00:00
|
|
|
(eprint
|
|
|
|
(if ec "\e[31m" "")
|
|
|
|
"parse error in "
|
|
|
|
where
|
|
|
|
" around line "
|
|
|
|
(string line)
|
|
|
|
", column "
|
|
|
|
(string col)
|
|
|
|
": "
|
|
|
|
(parser/error p)
|
2020-02-04 00:14:32 +00:00
|
|
|
(if ec "\e[0m" ""))
|
|
|
|
(eflush))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn bad-compile
|
|
|
|
"Default handler for a compile error."
|
|
|
|
[msg macrof where]
|
2019-09-22 22:18:28 +00:00
|
|
|
(def ec (dyn :err-color))
|
2020-01-20 22:05:08 +00:00
|
|
|
(if macrof
|
2020-01-20 22:45:57 +00:00
|
|
|
(debug/stacktrace macrof (string msg " while compiling " where))
|
2020-01-20 22:05:08 +00:00
|
|
|
(eprint
|
|
|
|
(if ec "\e[31m" "")
|
|
|
|
"compile error: "
|
|
|
|
msg
|
|
|
|
" while compiling "
|
|
|
|
where
|
2020-02-04 00:14:32 +00:00
|
|
|
(if ec "\e[0m" "")))
|
|
|
|
(eflush))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn run-context
|
2020-04-16 17:11:17 +00:00
|
|
|
"Run a context. This evaluates expressions in an environment,
|
2019-03-22 22:34:50 +00:00
|
|
|
and is encapsulates the parsing, compilation, and evaluation.
|
2020-04-05 02:12:05 +00:00
|
|
|
Returns (in environment :exit-value environment) when complete.
|
2019-03-22 22:34:50 +00:00
|
|
|
opts is a table or struct of options. The options are as follows:\n\n\t
|
|
|
|
:chunks - callback to read into a buffer - default is getline\n\t
|
|
|
|
:on-parse-error - callback when parsing fails - default is bad-parse\n\t
|
2019-04-16 19:41:45 +00:00
|
|
|
:env - the environment to compile against - default is the current env\n\t
|
2019-03-22 22:34:50 +00:00
|
|
|
:source - string path of source for better errors - default is \"<anonymous>\"\n\t
|
|
|
|
:on-compile-error - callback when compilation fails - default is bad-compile\n\t
|
2019-10-27 21:15:41 +00:00
|
|
|
:evaluator - callback that executes thunks. Signature is (evaluator thunk source env where)\n\t
|
2019-03-22 22:34:50 +00:00
|
|
|
:on-status - callback when a value is evaluated - default is debug/stacktrace\n\t
|
2019-10-11 03:59:43 +00:00
|
|
|
:fiber-flags - what flags to wrap the compilation fiber with. Default is :ia.\n\t
|
|
|
|
:expander - an optional function that is called on each top level form before being compiled."
|
2019-03-22 22:34:50 +00:00
|
|
|
[opts]
|
|
|
|
|
|
|
|
(def {:env env
|
|
|
|
:chunks chunks
|
|
|
|
:on-status onstatus
|
|
|
|
:on-compile-error on-compile-error
|
|
|
|
:on-parse-error on-parse-error
|
|
|
|
:fiber-flags guard
|
2019-10-27 21:15:41 +00:00
|
|
|
:evaluator evaluator
|
2019-10-11 03:59:43 +00:00
|
|
|
:source where
|
|
|
|
:expander expand} opts)
|
2019-04-16 19:41:45 +00:00
|
|
|
(default env (fiber/getenv (fiber/current)))
|
2020-01-18 05:03:50 +00:00
|
|
|
(default chunks (fn [buf p] (getline "" buf env)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(default onstatus debug/stacktrace)
|
|
|
|
(default on-compile-error bad-compile)
|
|
|
|
(default on-parse-error bad-parse)
|
2019-10-27 21:15:41 +00:00
|
|
|
(default evaluator (fn evaluate [x &] (x)))
|
2019-03-22 22:34:50 +00:00
|
|
|
(default where "<anonymous>")
|
2020-04-17 21:27:02 +00:00
|
|
|
(default guard :ydt)
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
# Are we done yet?
|
|
|
|
(var going true)
|
|
|
|
|
|
|
|
# The parser object
|
2020-05-21 17:47:47 +00:00
|
|
|
(def p (parser/new))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-04-16 19:41:45 +00:00
|
|
|
# Evaluate 1 source form in a protected manner
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn eval1 [source]
|
2019-10-11 03:59:43 +00:00
|
|
|
(def source (if expand (expand source) source))
|
2019-03-22 22:34:50 +00:00
|
|
|
(var good true)
|
2019-11-26 02:00:13 +00:00
|
|
|
(var resumeval nil)
|
2019-03-22 22:34:50 +00:00
|
|
|
(def f
|
|
|
|
(fiber/new
|
|
|
|
(fn []
|
|
|
|
(def res (compile source env where))
|
|
|
|
(if (= (type res) :function)
|
2019-10-27 21:15:41 +00:00
|
|
|
(evaluator res source env where)
|
2019-03-22 22:34:50 +00:00
|
|
|
(do
|
|
|
|
(set good false)
|
2019-09-22 22:18:28 +00:00
|
|
|
(def {:error err :line line :column column :fiber errf} res)
|
2019-03-22 22:34:50 +00:00
|
|
|
(def msg
|
2019-09-22 22:18:28 +00:00
|
|
|
(if (<= 0 line)
|
|
|
|
(string err " on line " line ", column " column)
|
2019-03-22 22:34:50 +00:00
|
|
|
err))
|
|
|
|
(on-compile-error msg errf where))))
|
2020-02-10 01:04:34 +00:00
|
|
|
guard))
|
2019-04-16 19:41:45 +00:00
|
|
|
(fiber/setenv f env)
|
2020-02-23 19:31:27 +00:00
|
|
|
(while (fiber/can-resume? f)
|
2019-11-26 02:00:13 +00:00
|
|
|
(def res (resume f resumeval))
|
|
|
|
(when good (when going (set resumeval (onstatus f res))))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2020-04-28 01:29:16 +00:00
|
|
|
(defn parse-err
|
|
|
|
"Handle parser error in the correct environment"
|
|
|
|
[p where]
|
|
|
|
(def f (coro (on-parse-error p where)))
|
|
|
|
(fiber/setenv f env)
|
|
|
|
(resume f))
|
|
|
|
|
2019-04-16 19:41:45 +00:00
|
|
|
# Loop
|
2019-03-22 22:34:50 +00:00
|
|
|
(def buf @"")
|
|
|
|
(while going
|
2019-10-11 03:59:43 +00:00
|
|
|
(if (env :exit) (break))
|
2019-03-22 22:34:50 +00:00
|
|
|
(buffer/clear buf)
|
2020-05-21 17:37:15 +00:00
|
|
|
(if (= (chunks buf p)
|
|
|
|
:cancel)
|
2020-05-20 21:40:05 +00:00
|
|
|
(do
|
2020-05-21 17:47:47 +00:00
|
|
|
# A :cancel chunk represents a cancelled form in the REPL, so reset.
|
2020-05-21 17:31:21 +00:00
|
|
|
(parser/flush p)
|
2020-05-20 21:40:05 +00:00
|
|
|
(buffer/clear buf))
|
|
|
|
(do
|
|
|
|
(var pindex 0)
|
|
|
|
(var pstatus nil)
|
|
|
|
(def len (length buf))
|
|
|
|
(when (= len 0)
|
|
|
|
(parser/eof p)
|
|
|
|
(set going false))
|
|
|
|
(while (> len pindex)
|
|
|
|
(+= pindex (parser/consume p buf pindex))
|
|
|
|
(while (parser/has-more p)
|
|
|
|
(eval1 (parser/produce p)))
|
|
|
|
(when (= (parser/status p) :error)
|
|
|
|
(parse-err p where))))))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
# Check final parser state
|
|
|
|
(while (parser/has-more p)
|
|
|
|
(eval1 (parser/produce p)))
|
|
|
|
(when (= (parser/status p) :error)
|
2020-04-28 01:29:16 +00:00
|
|
|
(parse-err p where))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
(in env :exit-value env))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-10-11 03:59:43 +00:00
|
|
|
(defn quit
|
|
|
|
"Tries to exit from the current repl or context. Does not always exit the application.
|
2019-11-24 23:45:53 +00:00
|
|
|
Works by setting the :exit dynamic binding to true. Passing a non-nil value here will cause the outer
|
|
|
|
run-context to return that value."
|
|
|
|
[&opt value]
|
2019-10-11 03:59:43 +00:00
|
|
|
(setdyn :exit true)
|
2019-11-24 23:45:53 +00:00
|
|
|
(setdyn :exit-value value)
|
|
|
|
nil)
|
2019-10-11 03:59:43 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn eval-string
|
|
|
|
"Evaluates a string in the current environment. If more control over the
|
|
|
|
environment is needed, use run-context."
|
2019-04-16 19:41:45 +00:00
|
|
|
[str]
|
2019-03-22 22:34:50 +00:00
|
|
|
(var state (string str))
|
|
|
|
(defn chunks [buf _]
|
|
|
|
(def ret state)
|
|
|
|
(set state nil)
|
|
|
|
(when ret
|
|
|
|
(buffer/push-string buf str)
|
|
|
|
(buffer/push-string buf "\n")))
|
|
|
|
(var returnval nil)
|
2019-04-16 19:41:45 +00:00
|
|
|
(run-context {:chunks chunks
|
2019-03-22 22:34:50 +00:00
|
|
|
:on-compile-error (fn [msg errf &]
|
|
|
|
(error (string "compile error: " msg)))
|
|
|
|
:on-parse-error (fn [p x]
|
|
|
|
(error (string "parse error: " (parser/error p))))
|
2019-04-16 19:41:45 +00:00
|
|
|
:fiber-flags :i
|
2019-03-22 22:34:50 +00:00
|
|
|
:on-status (fn [f val]
|
|
|
|
(if-not (= (fiber/status f) :dead)
|
|
|
|
(error val))
|
|
|
|
(set returnval val))
|
2019-04-16 19:41:45 +00:00
|
|
|
:source "eval-string"})
|
2019-03-22 22:34:50 +00:00
|
|
|
returnval)
|
|
|
|
|
|
|
|
(defn eval
|
|
|
|
"Evaluates a form in the current environment. If more control over the
|
|
|
|
environment is needed, use run-context."
|
2019-04-16 19:41:45 +00:00
|
|
|
[form]
|
|
|
|
(def res (compile form (fiber/getenv (fiber/current)) "eval"))
|
2019-03-22 22:34:50 +00:00
|
|
|
(if (= (type res) :function)
|
|
|
|
(res)
|
|
|
|
(error (res :error))))
|
|
|
|
|
2020-04-25 16:46:32 +00:00
|
|
|
(defn parse
|
|
|
|
"Parse a string and return the first value. For complex parsing, such as for a repl with error handling,
|
|
|
|
use the parser api."
|
|
|
|
[str]
|
|
|
|
(let [p (parser/new)]
|
|
|
|
(parser/consume p str)
|
|
|
|
(parser/eof p)
|
|
|
|
(if (parser/has-more p)
|
|
|
|
(parser/produce p)
|
|
|
|
(if (= :error (parser/status p))
|
|
|
|
(error (parser/error p))
|
|
|
|
(error "no value")))))
|
|
|
|
|
2019-11-27 05:13:53 +00:00
|
|
|
(def make-image-dict
|
|
|
|
"A table used in combination with marshal to marshal code (images), such that
|
|
|
|
(make-image x) is the same as (marshal x make-image-dict)."
|
|
|
|
@{})
|
|
|
|
|
|
|
|
(def load-image-dict
|
|
|
|
"A table used in combination with unmarshal to unmarshal byte sequences created
|
2019-12-02 03:53:39 +00:00
|
|
|
by make-image, such that (load-image bytes) is the same as (unmarshal bytes load-image-dict)."
|
2019-11-27 05:13:53 +00:00
|
|
|
@{})
|
|
|
|
|
2019-12-05 01:53:13 +00:00
|
|
|
(def comptime
|
|
|
|
"(comptime x)\n\n
|
|
|
|
Evals x at compile time and returns the result. Similar to a top level unquote."
|
2019-12-05 00:36:37 +00:00
|
|
|
:macro eval)
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn make-image
|
|
|
|
"Create an image from an environment returned by require.
|
|
|
|
Returns the image source as a string."
|
|
|
|
[env]
|
2019-11-27 05:13:53 +00:00
|
|
|
(marshal env make-image-dict))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(defn load-image
|
|
|
|
"The inverse operation to make-image. Returns an environment."
|
|
|
|
[image]
|
2019-11-27 05:13:53 +00:00
|
|
|
(unmarshal image load-image-dict))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-06-20 03:23:27 +00:00
|
|
|
(defn- check-. [x] (if (string/has-prefix? "." x) x))
|
|
|
|
(defn- not-check-. [x] (unless (string/has-prefix? "." x) x))
|
2019-06-19 02:01:14 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(def module/paths
|
2019-06-19 02:01:14 +00:00
|
|
|
"The list of paths to look for modules, templated for module/expand-path.
|
|
|
|
Each element is a two element tuple, containing the path
|
2019-03-22 22:34:50 +00:00
|
|
|
template and a keyword :source, :native, or :image indicating how
|
2019-05-25 20:13:02 +00:00
|
|
|
require should load files found at these paths.\n\nA tuple can also
|
|
|
|
contain a third element, specifying a filter that prevents module/find
|
|
|
|
from searching that path template if the filter doesn't match the input
|
2019-06-19 02:01:14 +00:00
|
|
|
path. The filter can be a string or a predicate function, and
|
|
|
|
is often a file extension, including the period."
|
2020-01-13 02:59:45 +00:00
|
|
|
@[])
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-07-20 21:57:07 +00:00
|
|
|
(setdyn :syspath (boot/opts "JANET_PATH"))
|
|
|
|
(setdyn :headerpath (boot/opts "JANET_HEADERPATH"))
|
2019-05-15 14:49:16 +00:00
|
|
|
|
2020-01-13 02:59:45 +00:00
|
|
|
(defn module/add-paths
|
|
|
|
"Add paths to module/paths for a given loader such that
|
|
|
|
the generated paths behave like other module types, including
|
|
|
|
relative imports and syspath imports. ext is the file extension
|
|
|
|
to associate with this module type, including the dot. loader is the
|
|
|
|
keyword name of a loader that is module/loaders. Returns the modified module/paths."
|
|
|
|
[ext loader]
|
|
|
|
(defn- find-prefix
|
|
|
|
[pre]
|
2020-04-19 14:38:18 +00:00
|
|
|
(or (find-index |(and (string? ($ 0)) (string/has-prefix? pre ($ 0))) module/paths) 0))
|
2020-01-13 02:59:45 +00:00
|
|
|
(def all-index (find-prefix ":all:"))
|
|
|
|
(array/insert module/paths all-index [(string ":all:" ext) loader not-check-.])
|
|
|
|
(def sys-index (find-prefix ":sys:"))
|
|
|
|
(array/insert module/paths sys-index [(string ":sys:/:all:" ext) loader not-check-.])
|
2020-05-12 13:46:26 +00:00
|
|
|
(def curall-index (find-prefix ":cur:/:all:"))
|
|
|
|
(array/insert module/paths curall-index [(string ":cur:/:all:" ext) loader check-.])
|
2020-01-13 02:59:45 +00:00
|
|
|
module/paths)
|
|
|
|
|
|
|
|
(module/add-paths ":native:" :native)
|
|
|
|
(module/add-paths "/init.janet" :source)
|
|
|
|
(module/add-paths ".janet" :source)
|
|
|
|
(module/add-paths ".jimage" :image)
|
|
|
|
|
2019-04-09 13:05:47 +00:00
|
|
|
# Version of fexists that works even with a reduced OS
|
2019-04-09 13:01:52 +00:00
|
|
|
(if-let [has-stat (_env 'os/stat)]
|
|
|
|
(let [stat (has-stat :value)]
|
|
|
|
(defglobal "fexists" (fn fexists [path] (= :file (stat path :mode)))))
|
|
|
|
(defglobal "fexists"
|
|
|
|
(fn fexists [path]
|
2019-07-12 13:14:37 +00:00
|
|
|
(def f (file/open path :rb))
|
2019-04-09 13:01:52 +00:00
|
|
|
(when f
|
|
|
|
(def res
|
|
|
|
(try (do (file/read f 1) true)
|
2020-05-05 14:17:09 +00:00
|
|
|
([err] nil)))
|
2019-04-09 13:01:52 +00:00
|
|
|
(file/close f)
|
|
|
|
res))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-05-25 20:13:02 +00:00
|
|
|
(defn- mod-filter
|
|
|
|
[x path]
|
|
|
|
(case (type x)
|
2019-05-25 21:10:25 +00:00
|
|
|
:nil path
|
2019-05-25 20:13:02 +00:00
|
|
|
:string (string/has-suffix? x path)
|
|
|
|
(x path)))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn module/find
|
|
|
|
"Try to match a module or path name from the patterns in module/paths.
|
|
|
|
Returns a tuple (fullpath kind) where the kind is one of :source, :native,
|
2019-04-16 19:41:45 +00:00
|
|
|
or image if the module is found, otherwise a tuple with nil followed by
|
2019-03-22 22:34:50 +00:00
|
|
|
an error message."
|
|
|
|
[path]
|
2019-05-25 20:13:02 +00:00
|
|
|
(var ret nil)
|
2019-05-25 21:27:56 +00:00
|
|
|
(each [p mod-kind checker] module/paths
|
2019-05-25 20:13:02 +00:00
|
|
|
(when (mod-filter checker path)
|
2019-05-25 21:27:56 +00:00
|
|
|
(if (function? p)
|
|
|
|
(when-let [res (p path)]
|
2020-05-05 14:17:09 +00:00
|
|
|
(set ret [res mod-kind])
|
|
|
|
(break))
|
2019-05-25 21:10:25 +00:00
|
|
|
(do
|
2019-06-19 02:01:14 +00:00
|
|
|
(def fullpath (string (module/expand-path path p)))
|
2019-05-25 21:10:25 +00:00
|
|
|
(when (fexists fullpath)
|
|
|
|
(set ret [fullpath mod-kind])
|
|
|
|
(break))))))
|
2019-05-25 20:13:02 +00:00
|
|
|
(if ret ret
|
|
|
|
(let [expander (fn [[t _ chk]]
|
2019-05-25 21:27:56 +00:00
|
|
|
(when (string? t)
|
|
|
|
(when (mod-filter chk path)
|
2019-06-19 02:01:14 +00:00
|
|
|
(module/expand-path path t))))
|
2019-05-25 20:13:02 +00:00
|
|
|
paths (filter identity (map expander module/paths))
|
|
|
|
str-parts (interpose "\n " paths)]
|
|
|
|
[nil (string "could not find module " path ":\n " ;str-parts)])))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(put _env 'fexists nil)
|
2019-05-25 20:13:02 +00:00
|
|
|
(put _env 'mod-filter nil)
|
2019-06-19 02:01:14 +00:00
|
|
|
(put _env 'check-. nil)
|
2019-06-19 13:01:21 +00:00
|
|
|
(put _env 'not-check-. nil)
|
2019-03-22 22:34:50 +00:00
|
|
|
|
|
|
|
(def module/cache
|
|
|
|
"Table mapping loaded module identifiers to their environments."
|
|
|
|
@{})
|
|
|
|
|
|
|
|
(def module/loading
|
|
|
|
"Table mapping currently loading modules to true. Used to prevent
|
|
|
|
circular dependencies."
|
|
|
|
@{})
|
|
|
|
|
2019-05-10 14:19:51 +00:00
|
|
|
(defn dofile
|
2020-04-05 02:12:05 +00:00
|
|
|
"Evaluate a file and return the resulting environment. :env, :expander, and
|
|
|
|
:evaluator are passed through to the underlying run-context call.
|
|
|
|
If exit is true, any top level errors will trigger a call to (os/exit 1)
|
|
|
|
after printing the error."
|
|
|
|
[path &keys
|
|
|
|
{:exit exit
|
|
|
|
:env env
|
2020-04-19 14:38:18 +00:00
|
|
|
:source src
|
2020-04-05 02:12:05 +00:00
|
|
|
:expander expander
|
|
|
|
:evaluator evaluator}]
|
2019-05-25 21:10:25 +00:00
|
|
|
(def f (if (= (type path) :core/file)
|
|
|
|
path
|
2019-07-12 13:14:37 +00:00
|
|
|
(file/open path :rb)))
|
2019-11-24 23:45:53 +00:00
|
|
|
(def path-is-file (= f path))
|
2019-05-27 20:50:57 +00:00
|
|
|
(default env (make-env))
|
2019-11-24 23:45:53 +00:00
|
|
|
(def spath (string path))
|
2020-04-19 14:38:18 +00:00
|
|
|
(put env :current-file (or src (if-not path-is-file spath)))
|
|
|
|
(put env :source (or src (if-not path-is-file spath path)))
|
2019-05-10 14:19:51 +00:00
|
|
|
(defn chunks [buf _] (file/read f 2048 buf))
|
|
|
|
(defn bp [&opt x y]
|
|
|
|
(def ret (bad-parse x y))
|
2020-04-05 02:12:05 +00:00
|
|
|
(if exit (os/exit 1))
|
2019-05-10 14:19:51 +00:00
|
|
|
ret)
|
|
|
|
(defn bc [&opt x y z]
|
|
|
|
(def ret (bad-compile x y z))
|
2020-04-05 02:12:05 +00:00
|
|
|
(if exit (os/exit 1))
|
2019-05-10 14:19:51 +00:00
|
|
|
ret)
|
2019-10-29 23:16:32 +00:00
|
|
|
(unless f
|
|
|
|
(error (string "could not find file " path)))
|
2019-11-24 23:45:53 +00:00
|
|
|
(def nenv
|
|
|
|
(run-context {:env env
|
|
|
|
:chunks chunks
|
|
|
|
:on-parse-error bp
|
|
|
|
:on-compile-error bc
|
|
|
|
:on-status (fn [f x]
|
|
|
|
(when (not= (fiber/status f) :dead)
|
|
|
|
(debug/stacktrace f x)
|
2020-04-05 02:12:05 +00:00
|
|
|
(if exit (os/exit 1) (eflush))))
|
2019-11-24 23:45:53 +00:00
|
|
|
:evaluator evaluator
|
|
|
|
:expander expander
|
2020-04-19 14:38:18 +00:00
|
|
|
:source (or src (if path-is-file "<anonymous>" spath))}))
|
2019-11-24 23:45:53 +00:00
|
|
|
(if-not path-is-file (file/close f))
|
|
|
|
nenv)
|
2019-05-10 14:19:51 +00:00
|
|
|
|
2019-05-16 16:05:40 +00:00
|
|
|
(def module/loaders
|
|
|
|
"A table of loading method names to loading functions.
|
|
|
|
This table lets require and import load many different kinds
|
|
|
|
of files as module."
|
|
|
|
@{:native (fn [path &] (native path (make-env)))
|
|
|
|
:source (fn [path args]
|
|
|
|
(put module/loading path true)
|
|
|
|
(def newenv (dofile path ;args))
|
|
|
|
(put module/loading path nil)
|
|
|
|
newenv)
|
|
|
|
:image (fn [path &] (load-image (slurp path)))})
|
|
|
|
|
2020-05-15 22:19:37 +00:00
|
|
|
(defn require-1
|
|
|
|
[path args kargs]
|
2019-06-19 02:01:14 +00:00
|
|
|
(def [fullpath mod-kind] (module/find path))
|
|
|
|
(unless fullpath (error mod-kind))
|
2020-05-15 22:19:37 +00:00
|
|
|
(if-let [check (if-not (kargs :fresh) (in module/cache fullpath))]
|
2019-03-22 22:34:50 +00:00
|
|
|
check
|
2020-04-19 14:44:02 +00:00
|
|
|
(if (module/loading fullpath)
|
2020-04-19 14:38:18 +00:00
|
|
|
(error (string "circular dependency " fullpath " detected"))
|
|
|
|
(do
|
|
|
|
(def loader (if (keyword? mod-kind) (module/loaders mod-kind) mod-kind))
|
|
|
|
(unless loader (error (string "module type " mod-kind " unknown")))
|
|
|
|
(def env (loader fullpath args))
|
|
|
|
(put module/cache fullpath env)
|
|
|
|
env))))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2020-05-15 22:19:37 +00:00
|
|
|
(defn require
|
|
|
|
"Require a module with the given name. Will search all of the paths in
|
|
|
|
module/paths. Returns the new environment
|
|
|
|
returned from compiling and running the file."
|
|
|
|
[path & args]
|
|
|
|
(require-1 path args (struct ;args)))
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn import*
|
2019-04-16 19:41:45 +00:00
|
|
|
"Function form of import. Same parameters, but the path
|
|
|
|
and other symbol parameters should be strings instead."
|
|
|
|
[path & args]
|
|
|
|
(def env (fiber/getenv (fiber/current)))
|
2020-05-15 22:19:37 +00:00
|
|
|
(def kargs (table ;args))
|
2019-03-22 22:34:50 +00:00
|
|
|
(def {:as as
|
|
|
|
:prefix prefix
|
2020-05-15 22:19:37 +00:00
|
|
|
:export ep} kargs)
|
|
|
|
(def newenv (require-1 path args kargs))
|
2020-01-24 22:54:06 +00:00
|
|
|
(def prefix (or
|
|
|
|
(and as (string as "/"))
|
|
|
|
prefix
|
|
|
|
(string (last (string/split "/" path)) "/")))
|
2019-04-17 01:44:19 +00:00
|
|
|
(loop [[k v] :pairs newenv :when (symbol? k) :when (not (v :private))]
|
2019-03-22 22:34:50 +00:00
|
|
|
(def newv (table/setproto @{:private (not ep)} v))
|
|
|
|
(put env (symbol prefix k) newv)))
|
|
|
|
|
2020-05-15 22:19:37 +00:00
|
|
|
(put _env 'require-1 nil)
|
|
|
|
|
2019-03-22 22:34:50 +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. One can also use :export true
|
2019-05-14 15:04:17 +00:00
|
|
|
to re-export the imported symbols. If :exit true is given as an argument,
|
|
|
|
any errors encountered at the top level in the module will cause (os/exit 1)
|
2020-05-15 22:19:37 +00:00
|
|
|
to be called. Dynamic bindings will NOT be imported. Use :fresh to bypass the
|
|
|
|
module cache."
|
2019-03-22 22:34:50 +00:00
|
|
|
[path & args]
|
2020-04-05 02:12:05 +00:00
|
|
|
(def argm (map |(if (keyword? $) $ (string $)) args))
|
2019-04-16 19:41:45 +00:00
|
|
|
(tuple import* (string path) ;argm))
|
2019-03-22 22:34:50 +00:00
|
|
|
|
2019-07-20 00:40:51 +00:00
|
|
|
(defmacro use
|
|
|
|
"Similar to import, but imported bindings are not prefixed with a namespace
|
|
|
|
identifier. Can also import multiple modules in one shot."
|
|
|
|
[& modules]
|
2020-04-05 02:12:05 +00:00
|
|
|
~(do ,;(map |~(,import* ,(string $) :prefix "") modules)))
|
2019-07-20 00:40:51 +00:00
|
|
|
|
2020-04-10 22:01:41 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Debugger
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(defn .fiber
|
|
|
|
"Get the current fiber being debugged."
|
|
|
|
[]
|
|
|
|
(dyn :fiber))
|
|
|
|
|
2020-04-10 23:29:10 +00:00
|
|
|
(defn .signal
|
|
|
|
"Get the current signal being debugged."
|
|
|
|
[]
|
|
|
|
(dyn :signal))
|
|
|
|
|
2020-04-10 22:01:41 +00:00
|
|
|
(defn .stack
|
|
|
|
"Print the current fiber stack"
|
|
|
|
[]
|
|
|
|
(print)
|
2020-04-10 23:29:10 +00:00
|
|
|
(with-dyns [:err-color false] (debug/stacktrace (.fiber) (.signal)))
|
2020-04-10 22:01:41 +00:00
|
|
|
(print))
|
|
|
|
|
|
|
|
(defn .frame
|
|
|
|
"Show a stack frame"
|
|
|
|
[&opt n]
|
|
|
|
(def stack (debug/stack (.fiber)))
|
|
|
|
(in stack (or n 0)))
|
|
|
|
|
|
|
|
(defn .fn
|
|
|
|
"Get the current function"
|
|
|
|
[&opt n]
|
|
|
|
(in (.frame n) :function))
|
|
|
|
|
|
|
|
(defn .slots
|
|
|
|
"Get an array of slots in a stack frame"
|
|
|
|
[&opt n]
|
|
|
|
(in (.frame n) :slots))
|
|
|
|
|
|
|
|
(defn .slot
|
|
|
|
"Get the value of the nth slot."
|
|
|
|
[&opt nth frame-idx]
|
|
|
|
(in (.slots frame-idx) (or nth 0)))
|
|
|
|
|
2020-04-29 04:04:24 +00:00
|
|
|
# Conditional compilation for disasm
|
|
|
|
(def disasm-alias (if-let [x (_env 'disasm)] (x :value)))
|
|
|
|
|
2020-04-10 22:01:41 +00:00
|
|
|
(defn .disasm
|
|
|
|
"Gets the assembly for the current function."
|
|
|
|
[&opt n]
|
|
|
|
(def frame (.frame n))
|
|
|
|
(def func (frame :function))
|
2020-04-29 04:04:24 +00:00
|
|
|
(disasm-alias func))
|
2020-04-10 22:01:41 +00:00
|
|
|
|
|
|
|
(defn .bytecode
|
|
|
|
"Get the bytecode for the current function."
|
|
|
|
[&opt n]
|
2020-05-19 18:17:50 +00:00
|
|
|
((.disasm n) :bytecode))
|
2020-04-10 22:01:41 +00:00
|
|
|
|
|
|
|
(defn .ppasm
|
|
|
|
"Pretty prints the assembly for the current function"
|
|
|
|
[&opt n]
|
|
|
|
(def frame (.frame n))
|
|
|
|
(def func (frame :function))
|
2020-04-29 04:04:24 +00:00
|
|
|
(def dasm (disasm-alias func))
|
2020-05-19 18:17:50 +00:00
|
|
|
(def bytecode (in dasm :bytecode))
|
2020-04-10 22:01:41 +00:00
|
|
|
(def pc (frame :pc))
|
2020-05-19 18:17:50 +00:00
|
|
|
(def sourcemap (in dasm :sourcemap))
|
2020-04-10 22:01:41 +00:00
|
|
|
(var last-loc [-2 -2])
|
2020-04-10 23:29:10 +00:00
|
|
|
(print "\n signal: " (.signal))
|
2020-05-19 18:17:50 +00:00
|
|
|
(print " function: " (dasm :name) " [" (in dasm :source "") "]")
|
|
|
|
(when-let [constants (dasm :constants)]
|
2020-04-10 22:01:41 +00:00
|
|
|
(printf " constants: %.4q" constants))
|
|
|
|
(printf " slots: %.4q\n" (frame :slots))
|
|
|
|
(def padding (string/repeat " " 20))
|
|
|
|
(loop [i :range [0 (length bytecode)]
|
|
|
|
:let [instr (bytecode i)]]
|
|
|
|
(prin (if (= (tuple/type instr) :brackets) "*" " "))
|
|
|
|
(prin (if (= i pc) "> " " "))
|
|
|
|
(prinf "%.20s" (string (string/join (map string instr) " ") padding))
|
|
|
|
(when sourcemap
|
|
|
|
(let [[sl sc] (sourcemap i)
|
|
|
|
loc [sl sc]]
|
|
|
|
(when (not= loc last-loc)
|
|
|
|
(set last-loc loc)
|
|
|
|
(prin " # line " sl ", column " sc))))
|
|
|
|
(print))
|
|
|
|
(print))
|
|
|
|
|
|
|
|
(defn .breakall
|
|
|
|
"Set breakpoints on all instructions in the current function."
|
|
|
|
[&opt n]
|
|
|
|
(def fun (.fn n))
|
|
|
|
(def bytecode (.bytecode n))
|
|
|
|
(for i 0 (length bytecode)
|
|
|
|
(debug/fbreak fun i))
|
|
|
|
(print "Set " (length bytecode) " breakpoints in " fun))
|
|
|
|
|
|
|
|
(defn .clearall
|
|
|
|
"Clear all breakpoints on the current function."
|
|
|
|
[&opt n]
|
|
|
|
(def fun (.fn n))
|
|
|
|
(def bytecode (.bytecode n))
|
|
|
|
(for i 0 (length bytecode)
|
|
|
|
(debug/unfbreak fun i))
|
|
|
|
(print "Cleared " (length bytecode) " breakpoints in " fun))
|
|
|
|
|
2020-05-12 13:50:37 +00:00
|
|
|
(unless (get _env 'disasm)
|
|
|
|
(put _env '.disasm nil)
|
|
|
|
(put _env '.bytecode nil)
|
|
|
|
(put _env '.breakall nil)
|
|
|
|
(put _env '.clearall nil)
|
|
|
|
(put _env '.ppasm nil))
|
|
|
|
(put _env 'disasm-alias nil)
|
|
|
|
|
|
|
|
(defn .source
|
|
|
|
"Show the source code for the function being debugged."
|
|
|
|
[&opt n]
|
|
|
|
(def frame (.frame n))
|
|
|
|
(def s (frame :source))
|
|
|
|
(def all-source (slurp s))
|
|
|
|
(print "\n" all-source "\n"))
|
|
|
|
|
2020-04-10 22:01:41 +00:00
|
|
|
(defn .break
|
|
|
|
"Set breakpoint at the current pc."
|
|
|
|
[]
|
|
|
|
(def frame (.frame))
|
|
|
|
(def fun (frame :function))
|
|
|
|
(def pc (frame :pc))
|
|
|
|
(debug/fbreak fun pc)
|
|
|
|
(print "Set breakpoint in " fun " at pc=" pc))
|
|
|
|
|
|
|
|
(defn .clear
|
|
|
|
"Clear the current breakpoint"
|
|
|
|
[]
|
|
|
|
(def frame (.frame))
|
|
|
|
(def fun (frame :function))
|
|
|
|
(def pc (frame :pc))
|
|
|
|
(debug/unfbreak fun pc)
|
|
|
|
(print "Cleared breakpoint in " fun " at pc=" pc))
|
|
|
|
|
|
|
|
(defn .next
|
|
|
|
"Go to the next breakpoint."
|
|
|
|
[&opt n]
|
|
|
|
(var res nil)
|
|
|
|
(for i 0 (or n 1)
|
|
|
|
(set res (resume (.fiber))))
|
|
|
|
res)
|
|
|
|
|
|
|
|
(defn .nextc
|
|
|
|
"Go to the next breakpoint, clearing the current breakpoint."
|
|
|
|
[&opt n]
|
|
|
|
(.clear)
|
|
|
|
(.next n))
|
|
|
|
|
|
|
|
(defn .step
|
|
|
|
"Execute the next n instructions."
|
|
|
|
[&opt n]
|
|
|
|
(var res nil)
|
|
|
|
(for i 0 (or n 1)
|
|
|
|
(set res (debug/step (.fiber))))
|
|
|
|
res)
|
|
|
|
|
2020-04-28 04:32:21 +00:00
|
|
|
(def debugger-env
|
|
|
|
"An environment that contains dot prefixed functions for debugging."
|
|
|
|
@{})
|
|
|
|
|
2020-04-10 22:01:41 +00:00
|
|
|
(def- debugger-keys (filter (partial string/has-prefix? ".") (keys _env)))
|
|
|
|
(each k debugger-keys (put debugger-env k (_env k)) (put _env k nil))
|
|
|
|
(put _env 'debugger-keys nil)
|
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### REPL
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
(defn repl
|
|
|
|
"Run a repl. The first parameter is an optional function to call to
|
|
|
|
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
|
2019-11-26 02:00:13 +00:00
|
|
|
caught. One can provide an optional environment table to run
|
2019-11-24 23:45:53 +00:00
|
|
|
the repl in."
|
2019-05-02 21:11:30 +00:00
|
|
|
[&opt chunks onsignal env]
|
|
|
|
(default env (make-env))
|
2020-04-05 02:12:05 +00:00
|
|
|
(default chunks
|
|
|
|
(fn [buf p]
|
|
|
|
(getline
|
|
|
|
(string
|
|
|
|
"repl:"
|
|
|
|
((parser/where p) 0)
|
|
|
|
":"
|
|
|
|
(parser/state p :delimiters) "> ")
|
|
|
|
buf env)))
|
2019-11-24 23:45:53 +00:00
|
|
|
(defn make-onsignal
|
|
|
|
[e level]
|
2019-11-26 02:00:13 +00:00
|
|
|
|
|
|
|
(defn enter-debugger
|
|
|
|
[f x]
|
|
|
|
(def nextenv (make-env env))
|
|
|
|
(put nextenv :fiber f)
|
|
|
|
(put nextenv :debug-level level)
|
|
|
|
(put nextenv :signal x)
|
2020-04-10 22:01:41 +00:00
|
|
|
(merge-into nextenv debugger-env)
|
2019-11-26 02:00:13 +00:00
|
|
|
(debug/stacktrace f x)
|
2020-02-04 00:14:32 +00:00
|
|
|
(eflush)
|
2019-11-26 02:00:13 +00:00
|
|
|
(defn debugger-chunks [buf p]
|
|
|
|
(def status (parser/state p :delimiters))
|
|
|
|
(def c ((parser/where p) 0))
|
2020-04-10 23:29:10 +00:00
|
|
|
(def prpt (string "debug[" level "]:" c ":" status "> "))
|
|
|
|
(getline prpt buf nextenv))
|
2019-11-26 02:00:13 +00:00
|
|
|
(print "entering debug[" level "] - (quit) to exit")
|
2020-02-04 00:14:32 +00:00
|
|
|
(flush)
|
2019-11-26 02:00:13 +00:00
|
|
|
(repl debugger-chunks (make-onsignal nextenv (+ 1 level)) nextenv)
|
|
|
|
(print "exiting debug[" level "]")
|
2020-02-04 00:14:32 +00:00
|
|
|
(flush)
|
2019-11-26 02:00:13 +00:00
|
|
|
(nextenv :resume-value))
|
|
|
|
|
2019-11-24 23:45:53 +00:00
|
|
|
(fn [f x]
|
2019-11-26 02:00:13 +00:00
|
|
|
(if (= :dead (fiber/status f))
|
2020-04-28 15:00:24 +00:00
|
|
|
(do
|
|
|
|
(put e '_ @{:value x})
|
|
|
|
(printf (get e :pretty-format "%q") x)
|
|
|
|
(flush))
|
2019-11-26 02:00:13 +00:00
|
|
|
(if (e :debug)
|
|
|
|
(enter-debugger f x)
|
2020-02-04 00:14:32 +00:00
|
|
|
(do (debug/stacktrace f x) (eflush))))))
|
2019-11-24 23:45:53 +00:00
|
|
|
|
2019-05-02 21:11:30 +00:00
|
|
|
(run-context {:env env
|
2019-03-22 22:34:50 +00:00
|
|
|
:chunks chunks
|
2019-11-26 02:00:13 +00:00
|
|
|
:on-status (or onsignal (make-onsignal env 1))
|
2019-03-22 22:34:50 +00:00
|
|
|
:source "repl"}))
|
|
|
|
|
2019-10-29 23:16:32 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### CLI Tool Main
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2020-02-22 03:10:42 +00:00
|
|
|
(defn- no-side-effects
|
|
|
|
"Check if form may have side effects. If returns true, then the src
|
|
|
|
must not have side effects, such as calling a C function."
|
|
|
|
[src]
|
|
|
|
(cond
|
|
|
|
(tuple? src)
|
|
|
|
(if (= (tuple/type src) :brackets)
|
|
|
|
(all no-side-effects src))
|
|
|
|
(array? src)
|
|
|
|
(all no-side-effects src)
|
|
|
|
(dictionary? src)
|
|
|
|
(and (all no-side-effects (keys src))
|
|
|
|
(all no-side-effects (values src)))
|
|
|
|
true))
|
|
|
|
|
|
|
|
(defn- is-safe-def [x] (no-side-effects (last x)))
|
|
|
|
|
|
|
|
(def- safe-forms {'defn true 'defn- true 'defmacro true 'defmacro- true
|
|
|
|
'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def
|
|
|
|
'defglobal is-safe-def 'varglobal is-safe-def})
|
|
|
|
|
|
|
|
(def- importers {'import true 'import* true 'use true 'dofile true 'require true})
|
|
|
|
|
2020-05-09 17:00:01 +00:00
|
|
|
# conditional compilation for reduced os
|
|
|
|
(def- getenv-alias (if-let [entry (in _env 'os/getenv)] (entry :value) (fn [&])))
|
|
|
|
|
2019-10-29 23:16:32 +00:00
|
|
|
(defn cli-main
|
|
|
|
"Entrance for the Janet CLI tool. Call this functions with the command line
|
|
|
|
arguments as an array or tuple of strings to invoke the CLI interface."
|
|
|
|
[args]
|
2019-10-29 23:28:41 +00:00
|
|
|
|
2019-10-29 23:16:32 +00:00
|
|
|
(setdyn :args args)
|
|
|
|
|
|
|
|
(var *should-repl* false)
|
|
|
|
(var *no-file* true)
|
|
|
|
(var *quiet* false)
|
|
|
|
(var *raw-stdin* false)
|
|
|
|
(var *handleopts* true)
|
|
|
|
(var *exit-on-error* true)
|
|
|
|
(var *colorize* true)
|
2020-04-10 22:01:41 +00:00
|
|
|
(var *debug* false)
|
2019-10-29 23:16:32 +00:00
|
|
|
(var *compile-only* false)
|
|
|
|
|
2020-05-09 17:00:01 +00:00
|
|
|
(if-let [jp (getenv-alias "JANET_PATH")] (setdyn :syspath jp))
|
|
|
|
(if-let [jp (getenv-alias "JANET_HEADERPATH")] (setdyn :headerpath jp))
|
2019-10-29 23:16:32 +00:00
|
|
|
|
|
|
|
# Flag handlers
|
2019-12-03 00:34:08 +00:00
|
|
|
(def handlers
|
2019-10-29 23:16:32 +00:00
|
|
|
{"h" (fn [&]
|
|
|
|
(print "usage: " (dyn :executable "janet") " [options] script args...")
|
|
|
|
(print
|
|
|
|
`Options are:
|
|
|
|
-h : Show this help
|
|
|
|
-v : Print the version string
|
|
|
|
-s : Use raw stdin instead of getline like functionality
|
|
|
|
-e code : Execute a string of janet
|
2020-04-10 22:01:41 +00:00
|
|
|
-d : Set the debug flag in the repl
|
2019-10-29 23:16:32 +00:00
|
|
|
-r : Enter the repl after running all scripts
|
|
|
|
-p : Keep on executing if there is a top level error (persistent)
|
2020-04-27 23:57:53 +00:00
|
|
|
-q : Hide logo (quiet)
|
2019-10-29 23:28:41 +00:00
|
|
|
-k : Compile scripts but do not execute (flycheck)
|
2019-10-29 23:16:32 +00:00
|
|
|
-m syspath : Set system path for loading global modules
|
|
|
|
-c source output : Compile janet source code into an image
|
|
|
|
-n : Disable ANSI color output in the repl
|
|
|
|
-l path : Execute code in a file before running the main script
|
|
|
|
-- : Stop handling options`)
|
|
|
|
(os/exit 0)
|
|
|
|
1)
|
|
|
|
"v" (fn [&] (print janet/version "-" janet/build) (os/exit 0) 1)
|
|
|
|
"s" (fn [&] (set *raw-stdin* true) (set *should-repl* true) 1)
|
|
|
|
"r" (fn [&] (set *should-repl* true) 1)
|
|
|
|
"p" (fn [&] (set *exit-on-error* false) 1)
|
|
|
|
"q" (fn [&] (set *quiet* true) 1)
|
|
|
|
"k" (fn [&] (set *compile-only* true) (set *exit-on-error* false) 1)
|
|
|
|
"n" (fn [&] (set *colorize* false) 1)
|
2019-11-08 23:35:27 +00:00
|
|
|
"m" (fn [i &] (setdyn :syspath (in args (+ i 1))) 2)
|
2019-10-29 23:16:32 +00:00
|
|
|
"c" (fn [i &]
|
2019-11-08 23:35:27 +00:00
|
|
|
(def e (dofile (in args (+ i 1))))
|
|
|
|
(spit (in args (+ i 2)) (make-image e))
|
2019-10-29 23:16:32 +00:00
|
|
|
(set *no-file* false)
|
|
|
|
3)
|
|
|
|
"-" (fn [&] (set *handleopts* false) 1)
|
|
|
|
"l" (fn [i &]
|
2019-11-08 23:35:27 +00:00
|
|
|
(import* (in args (+ i 1))
|
2019-10-29 23:16:32 +00:00
|
|
|
:prefix "" :exit *exit-on-error*)
|
|
|
|
2)
|
|
|
|
"e" (fn [i &]
|
|
|
|
(set *no-file* false)
|
2019-11-08 23:35:27 +00:00
|
|
|
(eval-string (in args (+ i 1)))
|
2020-04-10 22:01:41 +00:00
|
|
|
2)
|
|
|
|
"d" (fn [&] (set *debug* true) 1)})
|
2019-10-29 23:16:32 +00:00
|
|
|
|
|
|
|
(defn- dohandler [n i &]
|
2019-11-08 23:35:27 +00:00
|
|
|
(def h (in handlers n))
|
|
|
|
(if h (h i) (do (print "unknown flag -" n) ((in handlers "h")))))
|
2019-10-29 23:16:32 +00:00
|
|
|
|
|
|
|
(defn- evaluator
|
|
|
|
[thunk source env where]
|
|
|
|
(if *compile-only*
|
|
|
|
(when (tuple? source)
|
2020-02-22 03:10:42 +00:00
|
|
|
(def head (source 0))
|
|
|
|
(def safe-check (safe-forms head))
|
2019-10-29 23:16:32 +00:00
|
|
|
(cond
|
2020-02-22 03:10:42 +00:00
|
|
|
# Sometimes safe form
|
|
|
|
(function? safe-check)
|
|
|
|
(if (safe-check source) (thunk))
|
|
|
|
# Always safe form
|
|
|
|
safe-check
|
|
|
|
(thunk)
|
|
|
|
# Import-like form
|
|
|
|
(importers head)
|
2019-10-29 23:16:32 +00:00
|
|
|
(do
|
|
|
|
(let [[l c] (tuple/sourcemap source)
|
|
|
|
newtup (tuple/setmap (tuple ;source :evaluator evaluator) l c)]
|
|
|
|
((compile newtup env where))))))
|
|
|
|
(thunk)))
|
|
|
|
|
|
|
|
# Process arguments
|
|
|
|
(var i 0)
|
|
|
|
(def lenargs (length args))
|
|
|
|
(while (< i lenargs)
|
2019-11-08 23:35:27 +00:00
|
|
|
(def arg (in args i))
|
2019-10-29 23:16:32 +00:00
|
|
|
(if (and *handleopts* (= "-" (string/slice arg 0 1)))
|
2019-12-03 00:34:08 +00:00
|
|
|
(+= i (dohandler (string/slice arg 1) i))
|
2019-10-29 23:16:32 +00:00
|
|
|
(do
|
|
|
|
(set *no-file* false)
|
2019-12-04 14:30:36 +00:00
|
|
|
(def env (make-env))
|
2019-12-07 01:14:12 +00:00
|
|
|
(def subargs (array/slice args i))
|
|
|
|
(put env :args subargs)
|
2019-12-04 14:30:36 +00:00
|
|
|
(dofile arg :prefix "" :exit *exit-on-error* :evaluator evaluator :env env)
|
2019-12-07 01:14:12 +00:00
|
|
|
(if-let [main (get (in env 'main) :value)]
|
|
|
|
(let [thunk (compile [main ;(tuple/slice args i)] env arg)]
|
|
|
|
(if (function? thunk) (thunk) (error (thunk :error)))))
|
2019-10-29 23:16:32 +00:00
|
|
|
(set i lenargs))))
|
|
|
|
|
|
|
|
(when (and (not *compile-only*) (or *should-repl* *no-file*))
|
|
|
|
(if-not *quiet*
|
2020-01-12 16:50:37 +00:00
|
|
|
(print "Janet " janet/version "-" janet/build " Copyright (C) 2017-2020 Calvin Rose"))
|
2020-02-04 00:14:32 +00:00
|
|
|
(flush)
|
2019-10-29 23:16:32 +00:00
|
|
|
(defn getprompt [p]
|
|
|
|
(def [line] (parser/where p))
|
|
|
|
(string "janet:" line ":" (parser/state p :delimiters) "> "))
|
2020-01-18 05:03:50 +00:00
|
|
|
(defn getstdin [prompt buf _]
|
2019-10-29 23:16:32 +00:00
|
|
|
(file/write stdout prompt)
|
|
|
|
(file/flush stdout)
|
|
|
|
(file/read stdin :line buf))
|
2020-01-18 05:03:50 +00:00
|
|
|
(def env (make-env))
|
2020-04-10 22:01:41 +00:00
|
|
|
(if *debug* (put env :debug true))
|
2019-10-29 23:16:32 +00:00
|
|
|
(def getter (if *raw-stdin* getstdin getline))
|
|
|
|
(defn getchunk [buf p]
|
2020-04-21 00:28:30 +00:00
|
|
|
(getter (getprompt p) buf env))
|
2019-10-29 23:16:32 +00:00
|
|
|
(setdyn :pretty-format (if *colorize* "%.20Q" "%.20q"))
|
|
|
|
(setdyn :err-color (if *colorize* true))
|
2020-04-27 23:12:22 +00:00
|
|
|
(repl getchunk nil env)))
|
2019-10-29 23:16:32 +00:00
|
|
|
|
2020-02-22 03:10:42 +00:00
|
|
|
(put _env 'no-side-effects nil)
|
|
|
|
(put _env 'is-safe-def nil)
|
|
|
|
(put _env 'safe-forms nil)
|
|
|
|
(put _env 'importers nil)
|
2020-05-09 17:00:01 +00:00
|
|
|
(put _env 'getenv-alias nil)
|
2019-11-27 05:13:53 +00:00
|
|
|
|
|
|
|
###
|
|
|
|
###
|
|
|
|
### Clean up
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
2020-04-16 17:11:17 +00:00
|
|
|
(def root-env "The root environment used to create environments with (make-env)" _env)
|
2019-12-12 23:25:04 +00:00
|
|
|
|
2019-11-27 05:13:53 +00:00
|
|
|
(do
|
|
|
|
(put _env 'boot/opts nil)
|
|
|
|
(put _env '_env nil)
|
2019-12-12 23:25:04 +00:00
|
|
|
(def load-dict (env-lookup _env))
|
|
|
|
(merge-into load-image-dict load-dict)
|
|
|
|
(merge-into make-image-dict (invert load-dict)))
|
2019-11-27 05:13:53 +00:00
|
|
|
|
2019-03-22 22:34:50 +00:00
|
|
|
###
|
|
|
|
###
|
|
|
|
### Bootstrap
|
|
|
|
###
|
|
|
|
###
|
|
|
|
|
|
|
|
(do
|
2019-06-19 23:43:38 +00:00
|
|
|
|
|
|
|
(defn proto-flatten
|
|
|
|
"Flatten a table and it's prototypes into a single table."
|
|
|
|
[into x]
|
|
|
|
(when x
|
|
|
|
(proto-flatten into (table/getproto x))
|
|
|
|
(loop [k :keys x]
|
|
|
|
(put into k (x k))))
|
|
|
|
into)
|
|
|
|
|
2019-04-16 19:41:45 +00:00
|
|
|
(def env (fiber/getenv (fiber/current)))
|
2019-06-19 23:43:38 +00:00
|
|
|
|
|
|
|
# Modify env based on some options.
|
|
|
|
(loop [[k v] :pairs env
|
|
|
|
:when (symbol? k)]
|
|
|
|
(def flat (proto-flatten @{} v))
|
2019-07-20 21:57:07 +00:00
|
|
|
(when (boot/config :no-docstrings)
|
2019-06-19 23:43:38 +00:00
|
|
|
(put flat :doc nil))
|
2019-07-20 21:57:07 +00:00
|
|
|
(when (boot/config :no-sourcemaps)
|
2019-06-19 23:43:38 +00:00
|
|
|
(put flat :source-map nil))
|
|
|
|
(put env k flat))
|
|
|
|
|
2019-07-20 21:57:07 +00:00
|
|
|
(put env 'boot/config nil)
|
|
|
|
(put env 'boot/args nil)
|
2019-04-16 19:41:45 +00:00
|
|
|
(def image (let [env-pairs (pairs (env-lookup env))
|
2019-02-08 05:44:30 +00:00
|
|
|
essential-pairs (filter (fn [[k v]] (or (cfunction? v) (abstract? v))) env-pairs)
|
|
|
|
lookup (table ;(mapcat identity essential-pairs))
|
|
|
|
reverse-lookup (invert lookup)]
|
2019-04-16 19:41:45 +00:00
|
|
|
(marshal env reverse-lookup)))
|
2019-02-08 05:44:30 +00:00
|
|
|
|
2020-01-29 05:38:52 +00:00
|
|
|
# Create amalgamation
|
|
|
|
|
2020-03-31 12:50:40 +00:00
|
|
|
(def feature-header "src/core/features.h")
|
|
|
|
|
2020-01-29 05:38:52 +00:00
|
|
|
(def local-headers
|
2020-03-31 12:50:40 +00:00
|
|
|
["src/core/util.h"
|
2020-01-29 05:38:52 +00:00
|
|
|
"src/core/state.h"
|
|
|
|
"src/core/gc.h"
|
|
|
|
"src/core/vector.h"
|
|
|
|
"src/core/fiber.h"
|
|
|
|
"src/core/regalloc.h"
|
|
|
|
"src/core/compile.h"
|
|
|
|
"src/core/emit.h"
|
|
|
|
"src/core/symcache.h"])
|
|
|
|
|
|
|
|
(def core-sources
|
|
|
|
["src/core/abstract.c"
|
|
|
|
"src/core/array.c"
|
|
|
|
"src/core/asm.c"
|
|
|
|
"src/core/buffer.c"
|
|
|
|
"src/core/bytecode.c"
|
|
|
|
"src/core/capi.c"
|
|
|
|
"src/core/cfuns.c"
|
|
|
|
"src/core/compile.c"
|
|
|
|
"src/core/corelib.c"
|
|
|
|
"src/core/debug.c"
|
|
|
|
"src/core/emit.c"
|
|
|
|
"src/core/fiber.c"
|
|
|
|
"src/core/gc.c"
|
|
|
|
"src/core/inttypes.c"
|
|
|
|
"src/core/io.c"
|
|
|
|
"src/core/marsh.c"
|
|
|
|
"src/core/math.c"
|
2020-02-02 02:39:54 +00:00
|
|
|
"src/core/net.c"
|
2020-01-29 05:38:52 +00:00
|
|
|
"src/core/os.c"
|
|
|
|
"src/core/parse.c"
|
|
|
|
"src/core/peg.c"
|
|
|
|
"src/core/pp.c"
|
|
|
|
"src/core/regalloc.c"
|
|
|
|
"src/core/run.c"
|
|
|
|
"src/core/specials.c"
|
|
|
|
"src/core/string.c"
|
|
|
|
"src/core/strtod.c"
|
|
|
|
"src/core/struct.c"
|
|
|
|
"src/core/symcache.c"
|
|
|
|
"src/core/table.c"
|
|
|
|
"src/core/thread.c"
|
|
|
|
"src/core/tuple.c"
|
|
|
|
"src/core/typedarray.c"
|
|
|
|
"src/core/util.c"
|
|
|
|
"src/core/value.c"
|
|
|
|
"src/core/vector.c"
|
|
|
|
"src/core/vm.c"
|
|
|
|
"src/core/wrap.c"])
|
|
|
|
|
|
|
|
# Print janet.c to stdout
|
|
|
|
(print "/* Amalgamated build - DO NOT EDIT */")
|
|
|
|
(print "/* Generated from janet version " janet/version "-" janet/build " */")
|
|
|
|
(print "#define JANET_BUILD \"" janet/build "\"")
|
|
|
|
(print ```#define JANET_AMALG```)
|
|
|
|
|
2020-03-31 13:31:27 +00:00
|
|
|
(defn do-one-file
|
2020-01-29 05:38:52 +00:00
|
|
|
[fname]
|
2020-02-27 06:16:54 +00:00
|
|
|
(print "\n/* " fname " */")
|
|
|
|
(print "#line 0 \"" fname "\"\n")
|
2020-01-29 05:38:52 +00:00
|
|
|
(def source (slurp fname))
|
|
|
|
(print (string/replace-all "\r" "" source)))
|
|
|
|
|
2020-03-31 13:31:27 +00:00
|
|
|
(do-one-file feature-header)
|
2020-03-31 12:50:40 +00:00
|
|
|
|
|
|
|
(print ```#include "janet.h"```)
|
|
|
|
|
2020-01-29 05:38:52 +00:00
|
|
|
(each h local-headers
|
2020-03-31 13:31:27 +00:00
|
|
|
(do-one-file h))
|
2020-01-29 05:38:52 +00:00
|
|
|
|
2020-05-16 17:41:26 +00:00
|
|
|
# windows.h should not be included in any of the external or internal headers - only in .c files.
|
|
|
|
(print)
|
|
|
|
(print "/* Windows work around - winsock2 must be included before windows.h, especially in amalgamated build */")
|
|
|
|
(print "#if defined(JANET_WINDOWS) && defined(JANET_NET)")
|
|
|
|
(print "#include <winsock2.h>")
|
|
|
|
(print "#endif")
|
|
|
|
(print)
|
|
|
|
|
2020-01-29 05:38:52 +00:00
|
|
|
(each s core-sources
|
2020-03-31 13:31:27 +00:00
|
|
|
(do-one-file s))
|
2020-01-29 05:38:52 +00:00
|
|
|
|
2019-02-08 05:44:30 +00:00
|
|
|
# Create C source file that contains images a uint8_t buffer. This
|
|
|
|
# can be compiled and linked statically into the main janet library
|
|
|
|
# and example client.
|
2020-01-29 05:38:52 +00:00
|
|
|
(print "static const unsigned char janet_core_image_bytes[] = {")
|
|
|
|
(loop [line :in (partition 16 image)]
|
|
|
|
(prin " ")
|
|
|
|
(each b line
|
|
|
|
(prinf "0x%.2X, " b))
|
|
|
|
(print))
|
|
|
|
(print " 0\n};\n")
|
|
|
|
(print "const unsigned char *janet_core_image = janet_core_image_bytes;")
|
|
|
|
(print "size_t janet_core_image_size = sizeof(janet_core_image_bytes);"))
|