4192 lines
158 KiB
Text
4192 lines
158 KiB
Text
# Jolt Core Library
|
|
# Clojure-compatible core functions for the Jolt interpreter.
|
|
|
|
(use ./types)
|
|
(use ./phm)
|
|
(use ./regex)
|
|
(use ./config)
|
|
(use ./pv)
|
|
(use ./plist)
|
|
|
|
# ------------------------------------------------------------
|
|
# Vector representation helpers
|
|
#
|
|
# In immutable mode a vector value is a structural-sharing persistent vector
|
|
# (pvec); in mutable mode it is a plain Janet array. Janet tuples may also still
|
|
# appear (e.g. literals that have not been routed through make-vec), so the read
|
|
# helpers below accept tuple, pvec and (mutable mode) array uniformly.
|
|
# ------------------------------------------------------------
|
|
|
|
(defn jvec?
|
|
"True when x is a vector VALUE. In immutable mode that is a persistent vector
|
|
or tuple; in mutable mode vectors are plain arrays (so vectors and lists share
|
|
one fast representation — `vector?` is true for both)."
|
|
[x]
|
|
(if mutable?
|
|
(or (array? x) (tuple? x))
|
|
(or (tuple? x) (pvec? x))))
|
|
|
|
(defn vcount [x] (if (pvec? x) (pv-count x) (length x)))
|
|
(defn vnth [x i] (if (pvec? x) (pv-nth x i) (in x i)))
|
|
|
|
(defn vview
|
|
"An indexed (tuple/array) view of a vector value, for iteration/slicing."
|
|
[x]
|
|
(if (pvec? x) (pv->array x) x))
|
|
|
|
(defn make-vec
|
|
"Build a vector value from a Janet array/tuple of elements, honoring the
|
|
build-time collection mode."
|
|
[xs]
|
|
(if mutable? (array ;xs) (pv-from-indexed xs)))
|
|
|
|
(defn core-transient?
|
|
"True when x is a transient (a mutable scratch collection). See `transient`."
|
|
[x]
|
|
(and (table? x) (= :jolt/transient (get x :jolt/type))))
|
|
|
|
# Canonicalize a collection key/element to a value-hashable Janet struct/tuple so
|
|
# the PHM/PHS treat value-equal maps/vectors as the same key (Janet hashes tables
|
|
# by identity otherwise). Installed into phm via set-canonicalize-key!.
|
|
(var canon-key nil)
|
|
(set canon-key
|
|
(fn [k]
|
|
(cond
|
|
(pvec? k) (tuple ;(map canon-key (pv->array k)))
|
|
(plist? k) (tuple ;(map canon-key (pl->array k)))
|
|
(set? k) (do (def t @{}) (each e (phs-seq k) (put t (canon-key e) true)) (table/to-struct t))
|
|
(phm? k) (do (def t @{}) (each pair (phm-entries k) (put t (canon-key (in pair 0)) (canon-key (in pair 1)))) (table/to-struct t))
|
|
(and (table? k) (get k :jolt/deftype))
|
|
(do (def t @{}) (each kk (keys k) (when (not= kk :jolt/deftype) (put t kk (canon-key (get k kk))))) (table/to-struct t))
|
|
(struct? k) (do (def t @{}) (each kk (keys k) (put t (canon-key kk) (canon-key (get k kk)))) (table/to-struct t))
|
|
(array? k) (tuple ;(map canon-key k))
|
|
(tuple? k) (tuple ;(map canon-key k))
|
|
k)))
|
|
(set-canonicalize-key! canon-key)
|
|
|
|
(defn realize-for-iteration [c]
|
|
"Normalize a seqable to a Janet array/tuple for iteration: pvec -> array,
|
|
set -> seq, lazy-seq -> realized array; others pass through. Warning: will
|
|
loop on infinite lazy-seqs. Terminates on the empty cell, not on nil."
|
|
(cond
|
|
# nil is an empty seq in Clojure — iterating it yields nothing.
|
|
(nil? c) @[]
|
|
(pvec? c) (pv->array c)
|
|
(plist? c) (pl->array c)
|
|
(set? c) (phs-seq c)
|
|
(phm? c) (phm-entries c)
|
|
# byte array (Janet buffer) -> array of byte values
|
|
(buffer? c) (let [a @[]] (each x c (array/push a x)) a)
|
|
# struct map literal (no :jolt/type marker — not a symbol/char) -> entries
|
|
(and (struct? c) (nil? (get c :jolt/type))) (map (fn [k] (tuple k (get c k))) (keys c))
|
|
(lazy-seq? c)
|
|
(do
|
|
(var items @[])
|
|
(var cur c)
|
|
(var go true)
|
|
(while go
|
|
(let [cell (realize-ls cur)]
|
|
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
|
|
(set go false)
|
|
(do
|
|
(array/push items (in cell 0))
|
|
(let [rt (in cell 1)]
|
|
(if (nil? rt) (set go false) (set cur (make-lazy-seq rt))))))))
|
|
items)
|
|
c))
|
|
|
|
# ============================================================
|
|
# Predicates
|
|
# ============================================================
|
|
|
|
(defn core-char? [x] (and (struct? x) (= :jolt/char (x :jolt/type))))
|
|
(defn char-code [c] (c :ch))
|
|
(defn char->string [c] (string/from-bytes (c :ch)))
|
|
|
|
(defn core-nil? [x] (nil? x))
|
|
(defn core-not [x] (if x false true))
|
|
(defn core-some? [x] (not (nil? x)))
|
|
(defn core-string? [x] (string? x))
|
|
(defn core-number? [x] (number? x))
|
|
(defn core-fn? [x] (or (function? x) (cfunction? x)))
|
|
(defn core-keyword? [x] (keyword? x))
|
|
(defn core-symbol? [x] (and (struct? x) (= :symbol (x :jolt/type))))
|
|
(defn core-vector? [x] (jvec? x))
|
|
(defn core-map? [x] (or (phm? x) (struct? x) (if (and (table? x) (get x :jolt/deftype)) true false)))
|
|
# seq? is true only for actual sequences (lists, lazy-seqs) — NOT vectors, which
|
|
# are not ISeq in Clojure. (A Janet array represents a Clojure list/seq result.)
|
|
(defn core-seq? [x] (or (array? x) (plist? x) (lazy-seq? x)))
|
|
(defn core-coll? [x] (or (array? x) (tuple? x) (pvec? x) (plist? x) (struct? x) (phm? x) (set? x) (lazy-seq? x)))
|
|
|
|
(defn core-true? [x] (= true x))
|
|
(defn core-false? [x] (= false x))
|
|
(defn core-identical? [a b] (= a b))
|
|
|
|
# Strictness helpers: like Clojure, numeric ops reject non-numbers, and the
|
|
# integer ops (odd?/even?) reject non-integers (incl. infinities, NaN, fractions).
|
|
(defn- finite-num? [x] (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf)))
|
|
(defn- need-num [x op]
|
|
(if (number? x) x (error (string op " requires a number, got " (type x)))))
|
|
(defn- need-int [x op]
|
|
(if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x
|
|
(error (string op " requires an integer"))))
|
|
|
|
(defn core-zero? [x] (= (need-num x "zero?") 0))
|
|
(defn core-pos? [x] (> (need-num x "pos?") 0))
|
|
(defn core-neg? [x] (< (need-num x "neg?") 0))
|
|
(defn core-even? [n] (= 0 (% (need-int n "even?") 2)))
|
|
(defn core-odd? [n] (not= 0 (% (need-int n "odd?") 2)))
|
|
|
|
(defn core-integer? [x] (and (number? x) (= x (math/floor x))))
|
|
(defn core-boolean? [x] (or (= x true) (= x false)))
|
|
(defn core-list? [x] (or (plist? x) (and (array? x) (not (get x :jolt/type)))))
|
|
|
|
(defn core-empty? [coll]
|
|
(if (nil? coll) true
|
|
(if (set? coll) (= 0 (coll :cnt))
|
|
(if (phm? coll) (= 0 (coll :cnt))
|
|
(if (pvec? coll) (= 0 (pv-count coll))
|
|
(if (plist? coll) (pl-empty? coll)
|
|
(if (lazy-seq? coll) (nil? (ls-first coll))
|
|
(if (struct? coll) (= 0 (length (keys coll)))
|
|
(= 0 (length coll))))))))))
|
|
|
|
(defn core-every? [pred coll]
|
|
(var result true)
|
|
(each x (realize-for-iteration coll)
|
|
(if (not (pred x)) (do (set result false) (break))))
|
|
result)
|
|
|
|
# ============================================================
|
|
# Math — Clojure semantics (variadic, / with one arg = reciprocal)
|
|
# ============================================================
|
|
|
|
(def core-+ (fn [& args] (if (= 0 (length args)) 0 (+ ;args))))
|
|
|
|
(def core-sub
|
|
(fn [& args]
|
|
(if (= 0 (length args))
|
|
(error "Wrong number of args (0) passed to: -")
|
|
(apply - args))))
|
|
|
|
(def core-* (fn [& args] (if (= 0 (length args)) 1 (* ;args))))
|
|
|
|
(def core-/
|
|
(fn [& args]
|
|
(case (length args)
|
|
0 (error "Wrong number of args (0) passed to: /")
|
|
1 (/ 1 (args 0))
|
|
(apply / args))))
|
|
|
|
(def core-inc inc)
|
|
(def core-dec dec)
|
|
# Clojure integer division: quot truncates toward zero; rem matches the sign of
|
|
# the dividend; mod matches the sign of the divisor (floored).
|
|
(def core-quot (fn [n d]
|
|
(when (or (not (finite-num? n)) (not (finite-num? d))) (error "quot requires finite numbers"))
|
|
(when (= d 0) (error "Divide by zero"))
|
|
(let [q (/ n d)] (if (< q 0) (math/ceil q) (math/floor q)))))
|
|
(def core-rem (fn [n d] (- n (* (core-quot n d) d))))
|
|
(def core-mod (fn [n d]
|
|
(let [m (core-rem n d)]
|
|
(if (or (= m 0) (= (> n 0) (> d 0))) m (+ m d)))))
|
|
|
|
(defn core-max [& args] (each x args (need-num x "max")) (apply max args))
|
|
(defn core-min [& args] (each x args (need-num x "min")) (apply min args))
|
|
|
|
(defn core-abs [x] (if (neg? x) (- 0 x) x))
|
|
(defn core-rand [] (math/random))
|
|
(defn core-rand-int [n] (math/floor (* (math/random) n)))
|
|
|
|
# ============================================================
|
|
# Comparison
|
|
# ============================================================
|
|
|
|
(defn- eq-seqable
|
|
"If x is a Clojure sequential (vector/list/lazy-seq), return its elements as
|
|
an array; otherwise nil. Lets = compare across tuple/array/lazy-seq."
|
|
[x]
|
|
(cond
|
|
(lazy-seq? x) (realize-for-iteration x)
|
|
(pvec? x) (pv->array x)
|
|
(plist? x) (pl->array x)
|
|
(tuple? x) x
|
|
(array? x) x
|
|
nil))
|
|
|
|
(defn- eq-map-pairs
|
|
"Return [k v] pairs for a map-like value (phm/struct/table), else nil."
|
|
[x]
|
|
(cond
|
|
(phm? x) (phm-entries x)
|
|
(and (table? x) (get x :jolt/deftype)) nil
|
|
(struct? x) (pairs x)
|
|
(table? x) (pairs x)
|
|
nil))
|
|
|
|
(var jolt-equal? nil)
|
|
(set jolt-equal?
|
|
(fn [a b]
|
|
(let [sa (eq-seqable a) sb (eq-seqable b)]
|
|
(cond
|
|
# both sequential: compare element-wise (vectors/lists/lazy-seqs equal)
|
|
(and sa sb)
|
|
(if (= (length sa) (length sb))
|
|
(do (var ok true) (var i 0)
|
|
(while (and ok (< i (length sa)))
|
|
(unless (jolt-equal? (in sa i) (in sb i)) (set ok false))
|
|
(++ i))
|
|
ok)
|
|
false)
|
|
(or sa sb) false
|
|
# sets
|
|
(or (set? a) (set? b))
|
|
# value-based: same size and every element of a is value-equal to some
|
|
# element of b (so #{ {:a 1} } equals #{ (hash-map :a 1) } regardless of
|
|
# the elements' underlying representations)
|
|
(if (and (set? a) (set? b) (= (a :cnt) (b :cnt)))
|
|
(let [eb (phs-seq b)]
|
|
(var ok true)
|
|
(each x (phs-seq a)
|
|
(unless (some (fn [y] (jolt-equal? x y)) eb) (set ok false)))
|
|
ok)
|
|
false)
|
|
# maps: compare key/value pairs recursively, order-independent
|
|
true
|
|
(let [pa (eq-map-pairs a) pb (eq-map-pairs b)]
|
|
(if (or pa pb)
|
|
(if (and pa pb (= (length pa) (length pb)))
|
|
(do (var ok true)
|
|
(each pair pa
|
|
(let [k (in pair 0) v (in pair 1)
|
|
found (do (var fv :jolt/none)
|
|
(each p2 pb (when (jolt-equal? k (in p2 0)) (set fv (in p2 1))))
|
|
fv)]
|
|
(unless (and (not= found :jolt/none) (jolt-equal? v found)) (set ok false))))
|
|
ok)
|
|
false)
|
|
(deep= a b)))))))
|
|
|
|
(defn core-= [& args]
|
|
(if (< (length args) 2) true
|
|
(do
|
|
(var ok true)
|
|
(var i 0)
|
|
(while (and ok (< i (dec (length args))))
|
|
(unless (jolt-equal? (args i) (args (+ i 1))) (set ok false))
|
|
(++ i))
|
|
ok)))
|
|
|
|
(defn core-not= [& args] (not (apply core-= args)))
|
|
|
|
# Comparisons are variadic: (< a b c) means a < b < c.
|
|
(defn- chain-cmp [op opname xs]
|
|
# 1-arity (e.g. (< x)) is true regardless of x and does no type check.
|
|
(when (>= (length xs) 2) (each x xs (need-num x opname)))
|
|
(var ok true) (var i 0)
|
|
(while (and ok (< i (dec (length xs))))
|
|
(unless (op (in xs i) (in xs (+ i 1))) (set ok false))
|
|
(++ i))
|
|
ok)
|
|
(defn core-< [& xs] (chain-cmp < "<" xs))
|
|
(defn core-> [& xs] (chain-cmp > ">" xs))
|
|
(defn core-<= [& xs] (chain-cmp <= "<=" xs))
|
|
(defn core->= [& xs] (chain-cmp >= ">=" xs))
|
|
|
|
# ============================================================
|
|
# Collections
|
|
# ============================================================
|
|
|
|
# Is x a map value (for conj/merge semantics: conj-ing a map merges its entries)?
|
|
(defn- map-value? [x]
|
|
(or (phm? x) (and (struct? x) (nil? (get x :jolt/type)))))
|
|
|
|
(defn core-conj [& args]
|
|
(if (= 0 (length args)) (make-vec @[]) # (conj) -> []
|
|
(let [coll (first args) xs (tuple/slice args 1)]
|
|
(if (nil? coll)
|
|
# conj onto nil builds a list (prepends): (conj nil 1 2) -> (2 1)
|
|
(do (var result nil) (each x xs (set result (pl-cons x result))) result)
|
|
(if (pvec? coll)
|
|
(do (var result coll) (each x xs (set result (pv-conj result x))) result)
|
|
(if (plist? coll)
|
|
# list: prepend, O(1) per element via structural sharing
|
|
(do (var result coll) (each x xs (set result (pl-cons x result))) result)
|
|
(if (tuple? coll)
|
|
(tuple/slice (tuple ;(array/concat (array/slice coll) xs)))
|
|
(if (array? coll)
|
|
(if mutable?
|
|
# mutable mode: arrays are vectors — append in place
|
|
(do (each x xs (array/push coll x)) coll)
|
|
# immutable mode: arrays are lists — prepend onto a persistent cons node,
|
|
# sharing the original array as the tail (O(1) per element, no copy)
|
|
(do (var result coll) (each x xs (set result (pl-cons x result))) result))
|
|
(if (set? coll)
|
|
(apply phs-conj coll xs)
|
|
(if (phm? coll)
|
|
(do
|
|
(var result coll)
|
|
(each x xs
|
|
(if (map-value? x)
|
|
# conj a map -> merge its entries
|
|
(each k (if (phm? x) (keys (phm-to-struct x)) (keys x))
|
|
(set result (phm-assoc result k (if (phm? x) (phm-get x k) (in x k)))))
|
|
(set result (phm-assoc result (vnth x 0) (vnth x 1)))))
|
|
result)
|
|
(do
|
|
(var result coll)
|
|
(each x xs
|
|
(if (map-value? x)
|
|
(set result (merge result (if (phm? x) (phm-to-struct x) x)))
|
|
(set result (merge result {(vnth x 0) (vnth x 1)}))))
|
|
result)))))))))))
|
|
|
|
(defn core-assoc [m & kvs]
|
|
(when (odd? (length kvs))
|
|
(error "assoc expects an even number of key/value arguments"))
|
|
# assoc is defined on maps, vectors and nil; reject other shapes
|
|
(when (or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
|
(plist? m) (set? m) (core-transient? m)
|
|
(and (struct? m) (get m :jolt/type)))
|
|
(error (string "assoc requires a map or vector, got " (type m))))
|
|
(cond
|
|
(phm? m)
|
|
(do (var result m) (var i 0) (while (< i (length kvs)) (set result (phm-assoc result (kvs i) (kvs (+ i 1)))) (+= i 2)) result)
|
|
(pvec? m)
|
|
(do (var result m) (var i 0)
|
|
(while (< i (length kvs))
|
|
(let [idx (kvs i)]
|
|
(when (not (and (number? idx) (= idx (math/floor idx)) (>= idx 0) (<= idx (pv-count result))))
|
|
(error (string "Index " idx " out of bounds for assoc on a vector of length " (pv-count result))))
|
|
(set result (pv-assoc result idx (kvs (+ i 1)))))
|
|
(+= i 2)) result)
|
|
# vector: assoc by integer index (appending at count is allowed); stays a vector
|
|
(or (tuple? m) (array? m))
|
|
(do (var result (array/slice m)) (var i 0)
|
|
(while (< i (length kvs))
|
|
(let [idx (kvs i) v (kvs (+ i 1))]
|
|
(when (not (and (number? idx) (= idx (math/floor idx)) (>= idx 0) (<= idx (length result))))
|
|
(error (string "Index " idx " out of bounds for assoc on a vector of length " (length result))))
|
|
(if (= idx (length result)) (array/push result v) (put result idx v)))
|
|
(+= i 2))
|
|
(if (tuple? m) (tuple/slice (tuple ;result)) result))
|
|
# map (struct/table). If any key is a collection, a Janet struct/table keys
|
|
# it by identity — promote to a phm so such keys compare by value.
|
|
(let [coll-key (do (var c false) (var i 0)
|
|
(while (< i (length kvs))
|
|
(when (let [k (in kvs i)] (or (table? k) (array? k))) (set c true))
|
|
(+= i 2)) c)]
|
|
(if coll-key
|
|
(do (var result (make-phm))
|
|
(when m (each k (keys m) (set result (phm-assoc result k (get m k)))))
|
|
(var i 0) (while (< i (length kvs)) (set result (phm-assoc result (in kvs i) (in kvs (+ i 1)))) (+= i 2))
|
|
result)
|
|
(do (var result @{}) (when m (each k (keys m) (put result k (get m k))))
|
|
(var i 0) (while (< i (length kvs)) (let [k (kvs i) v (kvs (+ i 1))] (put result k v) (+= i 2)))
|
|
(if (struct? m) (table/to-struct result) result))))))
|
|
|
|
(defn core-dissoc [m & ks]
|
|
(cond
|
|
(nil? m) nil
|
|
(phm? m) (do (var result m) (each k ks (set result (phm-dissoc result k))) result)
|
|
# reject clearly non-map values (scalars, sequences, sets, symbol/char structs)
|
|
(or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
|
(pvec? m) (plist? m) (tuple? m) (array? m) (set? m) (core-transient? m)
|
|
(and (struct? m) (get m :jolt/type)))
|
|
(error (string "dissoc requires a map, got " (type m)))
|
|
# struct map / sorted-map / record / meta-wrapped map
|
|
(do (var result @{}) (each k (keys m) (var in-ks false) (each k2 ks (if (deep= k k2) (do (set in-ks true) (break)))) (if (not in-ks) (put result k (m k))))
|
|
(if (struct? m) (table/to-struct result) result))))
|
|
|
|
(defn core-get [m k &opt default]
|
|
(default default nil)
|
|
(if (nil? m) default
|
|
(if (core-transient? m)
|
|
(case (m :kind)
|
|
:vector (if (and (number? k) (>= k 0) (< k (length (m :arr)))) (in (m :arr) k) default)
|
|
:map (let [p (get (m :tbl) (canon-key k))] (if p (in p 1) default))
|
|
:set (if (nil? (get (m :tbl) (canon-key k))) default k))
|
|
(if (set? m) (phs-get m k default)
|
|
(if (phm? m) (phm-get m k default)
|
|
(if (pvec? m)
|
|
(if (and (number? k) (>= k 0) (< k (pv-count m))) (pv-nth m k) default)
|
|
(if (or (struct? m) (table? m))
|
|
(let [v (m k)]
|
|
(if (nil? v) default v))
|
|
(if (and (or (tuple? m) (array? m)) (number? k) (>= k 0) (< k (length m)))
|
|
(in m k)
|
|
default))))))))
|
|
|
|
# Runtime invoke dispatch for COMPILED code (interpreter uses evaluator's
|
|
# jolt-invoke). Handles real functions plus Clojure IFn collections.
|
|
(defn jolt-call [f & args]
|
|
(cond
|
|
(or (function? f) (cfunction? f)) (apply f args)
|
|
(keyword? f) (core-get (get args 0) f (get args 1))
|
|
(and (struct? f) (= :symbol (f :jolt/type))) (core-get (get args 0) f (get args 1))
|
|
(phm? f) (phm-get f (get args 0) (get args 1))
|
|
(set? f) (if (phs-contains? f (get args 0)) (get args 0) (get args 1))
|
|
(pvec? f)
|
|
(let [k (get args 0)]
|
|
(if (and (number? k) (= k (math/floor k)) (>= k 0) (< k (pv-count f)))
|
|
(pv-nth f k)
|
|
(error (string "Index " k " out of bounds for vector of length " (pv-count f)))))
|
|
(or (tuple? f) (array? f))
|
|
(let [k (get args 0)]
|
|
(if (and (number? k) (= k (math/floor k)) (>= k 0) (< k (length f)))
|
|
(in f k)
|
|
(error (string "Index " k " out of bounds for vector of length " (length f)))))
|
|
(or (struct? f) (and (table? f) (get f :jolt/deftype)))
|
|
(let [v (get f (get args 0) :jolt/not-found)]
|
|
(if (= v :jolt/not-found) (get args 1) v))
|
|
(error (string "Cannot call " (type f) " as a function"))))
|
|
|
|
(defn core-apply
|
|
"(apply f a b ... coll) — call f with the leading args plus the elements of
|
|
the final collection spliced in. Materializes pvec/lazy-seq/set tails."
|
|
[f & args]
|
|
(let [n (length args)]
|
|
(if (= n 0)
|
|
(jolt-call f)
|
|
(let [fixed (array/slice args 0 (- n 1))
|
|
t (in args (- n 1))
|
|
tail (cond (set? t) (phs-seq t) (phm? t) (tuple ;(phm-entries t))
|
|
(realize-for-iteration t))]
|
|
(jolt-call f ;fixed ;tail)))))
|
|
|
|
(defn core-get-in [m ks &opt default]
|
|
(default default nil)
|
|
(def ks (vview ks))
|
|
(var current m)
|
|
(var i 0)
|
|
(while (< i (length ks))
|
|
(if (nil? current) (break))
|
|
(set current (core-get current (ks i)))
|
|
(++ i))
|
|
(if (nil? current) default current))
|
|
|
|
(defn core-contains? [coll key]
|
|
(if (core-transient? coll)
|
|
(case (coll :kind)
|
|
:vector (and (number? key) (>= key 0) (< key (length (coll :arr))))
|
|
(not (nil? (get (coll :tbl) (canon-key key)))))
|
|
(if (set? coll) (phs-contains? coll key)
|
|
(if (phm? coll) (let [b (get (coll :buckets) (phm-hash-key key))] (if b (phm-bucket-contains? b key) false))
|
|
(if (pvec? coll) (and (number? key) (>= key 0) (< key (pv-count coll)))
|
|
(if (struct? coll) (not (nil? (coll key)))
|
|
(if (table? coll) (not (nil? (coll key)))
|
|
(if (or (tuple? coll) (array? coll))
|
|
(and (number? key) (>= key 0) (< key (length coll)))
|
|
false))))))))
|
|
|
|
# Coerce a Clojure IFn value to a Janet-callable fn for higher-order fns
|
|
# (map/filter/sort-by/group-by/...). Janet functions pass through; a keyword or
|
|
# symbol becomes a key lookup, a map a key lookup, a set a membership test — so
|
|
# (map :k coll), (sort-by :k coll), (filter a-set coll) work.
|
|
(defn- as-fn [f]
|
|
(cond
|
|
(or (function? f) (cfunction? f)) f
|
|
(keyword? f) (fn [x &opt d] (core-get x f d))
|
|
(core-symbol? f) (fn [x &opt d] (core-get x f d))
|
|
(phm? f) (fn [k &opt d] (core-get f k d))
|
|
(set? f) (fn [x &opt d] (if (core-contains? f x) x d))
|
|
true f))
|
|
|
|
# Sorted collections — minimal: backed by a struct (map) / sorted array (set),
|
|
# ordered by key/element on read. Defined early so seq/count/get can dispatch.
|
|
(defn core-sorted-map? [x] (and (table? x) (= :jolt/sorted-map (x :jolt/type))))
|
|
(defn core-sorted-set? [x] (and (table? x) (= :jolt/sorted-set (x :jolt/type))))
|
|
(defn sm-make [m] @{:jolt/type :jolt/sorted-map :map m})
|
|
(defn ss-make [items] @{:jolt/type :jolt/sorted-set :items items})
|
|
(defn core-sorted-map [& kvs]
|
|
(var m @{}) (var i 0)
|
|
(while (< i (length kvs)) (put m (kvs i) (kvs (+ i 1))) (+= i 2))
|
|
(sm-make (table/to-struct m)))
|
|
(defn core-sorted-set [& xs]
|
|
(var seen @{}) (each x xs (put seen x true))
|
|
(ss-make (sort (array ;(keys seen)))))
|
|
(defn sorted-map-keys [sm] (sort (array ;(keys (sm :map)))))
|
|
(defn sorted-map-entries [sm] (let [m (sm :map)] (map (fn [k] [k (get m k)]) (sorted-map-keys sm))))
|
|
|
|
(defn core-count [coll]
|
|
(cond
|
|
(nil? coll) 0
|
|
(core-transient? coll) (length (if (= :vector (coll :kind)) (coll :arr) (coll :tbl)))
|
|
(core-sorted-map? coll) (length (keys (coll :map)))
|
|
(core-sorted-set? coll) (length (coll :items))
|
|
(lazy-seq? coll) (ls-count coll)
|
|
(pvec? coll) (pv-count coll)
|
|
(plist? coll) (pl-count coll)
|
|
(set? coll) (coll :cnt)
|
|
(phm? coll) (coll :cnt)
|
|
(and (table? coll) (get coll :jolt/deftype)) (- (length (keys coll)) 1)
|
|
(or (string? coll) (buffer? coll) (struct? coll) (tuple? coll) (array? coll)) (length coll)
|
|
# count is undefined on scalars (numbers/keywords/symbols/booleans/chars)
|
|
(error (string "count not supported on " (type coll)))))
|
|
|
|
(defn core-first [coll]
|
|
(cond
|
|
(core-sorted-map? coll) (let [e (sorted-map-entries coll)] (if (empty? e) nil (in e 0)))
|
|
(core-sorted-set? coll) (let [i (coll :items)] (if (empty? i) nil (in i 0)))
|
|
(lazy-seq? coll) (ls-first coll)
|
|
(pvec? coll) (if (= 0 (pv-count coll)) nil (pv-nth coll 0))
|
|
(plist? coll) (if (pl-empty? coll) nil (pl-first coll))
|
|
# maps and sets: first of their seq (an entry / element)
|
|
(phm? coll) (let [e (phm-entries coll)] (if (= 0 (length e)) nil (in e 0)))
|
|
(set? coll) (let [s (phs-seq coll)] (if (= 0 (length s)) nil (in s 0)))
|
|
(and (struct? coll) (nil? (get coll :jolt/type)))
|
|
(let [ks (keys coll)] (if (= 0 (length ks)) nil (tuple (in ks 0) (get coll (in ks 0)))))
|
|
(nil? coll) nil
|
|
(string? coll) (if (= 0 (length coll)) nil (make-char (in coll 0)))
|
|
# scalars aren't seqable
|
|
(or (number? coll) (boolean? coll) (keyword? coll) (and (struct? coll) (get coll :jolt/type)))
|
|
(error (string "first not supported on " (type coll)))
|
|
(= 0 (length coll)) nil
|
|
(in coll 0)))
|
|
|
|
(defn core-rest [coll]
|
|
(cond
|
|
(lazy-seq? coll) (ls-rest coll)
|
|
(plist? coll) (pl-rest coll)
|
|
(pvec? coll) (let [a (pv->array coll)] (if (<= (length a) 1) @[] (array/slice a 1)))
|
|
(or (nil? coll) (= 0 (length coll))) @[]
|
|
(string? coll) (tuple ;(map make-char (string/bytes (string/slice coll 1))))
|
|
(tuple? coll) (tuple/slice coll 1)
|
|
(array/slice coll 1)))
|
|
|
|
(defn core-next [coll]
|
|
(let [r (core-rest coll)]
|
|
(if (= 0 (length r)) nil r)))
|
|
|
|
(defn core-cons [x coll]
|
|
"Prepend x onto coll. For concrete collections this is an O(1) persistent cons
|
|
node; for lazy-seqs it stays a lazy cell so laziness is preserved."
|
|
(cond
|
|
(lazy-seq? coll) @[x (fn [] coll)]
|
|
(or (nil? coll) (plist? coll) (array? coll) (tuple? coll)) (pl-cons x coll)
|
|
# second arg must be seqable (a collection or string); reject scalars
|
|
(not (or (core-coll? coll) (string? coll)))
|
|
(error (string "Don't know how to create ISeq from: " (type coll)))
|
|
(pl-cons x (realize-for-iteration coll))))
|
|
|
|
(defn core-seq [coll]
|
|
(cond
|
|
(core-sorted-map? coll) (let [e (sorted-map-entries coll)] (if (empty? e) nil (tuple ;e)))
|
|
(core-sorted-set? coll) (let [i (coll :items)] (if (empty? i) nil (tuple ;i)))
|
|
(or (nil? coll) (and (or (tuple? coll) (array? coll)) (= 0 (length coll)))) nil
|
|
(lazy-seq? coll) (ls-seq coll)
|
|
(pvec? coll) (if (= 0 (pv-count coll)) nil (tuple ;(pv->array coll)))
|
|
(plist? coll) (if (pl-empty? coll) nil (tuple ;(pl->array coll)))
|
|
(buffer? coll) (if (= 0 (length coll)) nil (let [a @[]] (each x coll (array/push a x)) (tuple ;a)))
|
|
(set? coll) (phs-seq coll)
|
|
(phm? coll) (tuple ;(phm-entries coll))
|
|
(tuple? coll) (tuple/slice coll)
|
|
(string? coll) (if (= 0 (length coll)) nil (tuple ;(map make-char (string/bytes coll))))
|
|
(struct? coll) (tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll)))
|
|
(array? coll) (tuple ;coll)
|
|
(and (table? coll) (get coll :jolt/deftype)) coll
|
|
# scalars/functions aren't seqable
|
|
(error (string "seq not supported on " (type coll)))))
|
|
|
|
(defn core-vec [coll]
|
|
(when (not (or (nil? coll) (core-coll? coll) (string? coll)))
|
|
(error (string "Don't know how to create a vector from " (type coll))))
|
|
(let [coll (realize-for-iteration coll)]
|
|
(cond
|
|
(array? coll) (make-vec coll)
|
|
(tuple? coll) (make-vec coll)
|
|
(struct? coll) (make-vec (map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2))))
|
|
(string? coll) (make-vec (map |(string/from-bytes $) (string/bytes coll)))
|
|
(make-vec @[]))))
|
|
|
|
(defn- into-conj [to items]
|
|
(cond
|
|
(or (phm? to) (struct? to) (and (table? to) (get to :jolt/deftype)))
|
|
(do (var result to)
|
|
(each item items (set result (core-assoc result (vnth item 0) (vnth item 1))))
|
|
result)
|
|
(pvec? to) (do (var result to) (each x items (set result (pv-conj result x))) result)
|
|
(array? to) (if mutable?
|
|
(do (each x items (array/push to x)) to) # vector: append
|
|
(do (var result (array/slice to)) (each x items (array/insert result 0 x)) result)) # list: prepend
|
|
(tuple? to) (tuple/slice (tuple ;(array/concat (array/slice to) (array/slice items))))
|
|
to))
|
|
|
|
(defn core-merge [& maps]
|
|
# Clojure: (when (some identity maps) (reduce conj (or (first maps) {}) (rest maps)))
|
|
# - (merge) and (merge nil nil) -> nil; nil args elsewhere are no-ops.
|
|
# - later args follow conj semantics (a map merges its entries; a [k v]
|
|
# vector/map-entry adds that entry).
|
|
(var any false)
|
|
(each m maps (when (not (nil? m)) (set any true)))
|
|
(if (not any)
|
|
nil
|
|
(do
|
|
(var result (let [f (in maps 0)] (if (nil? f) (struct) f)))
|
|
(var i 1)
|
|
(while (< i (length maps))
|
|
(let [m (in maps i)]
|
|
(cond
|
|
(nil? m) nil
|
|
(or (phm? m) (struct? m))
|
|
(each k (if (phm? m) (keys (phm-to-struct m)) (keys m))
|
|
(set result (core-assoc result k (if (phm? m) (phm-get m k) (in m k)))))
|
|
# a [k v] pair (map-entry / 2-vector), per conj
|
|
(and (or (pvec? m) (tuple? m) (array? m))
|
|
(= 2 (if (pvec? m) (pv-count m) (length m))))
|
|
(set result (core-assoc result (vnth m 0) (vnth m 1)))
|
|
# scalars, sets, and wrong-length sequentials can't merge into a map
|
|
# (a length-2 vector was handled above; anything else here is bad)
|
|
(or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
|
(set? m) (plist? m) (pvec? m) (tuple? m) (array? m)
|
|
(and (struct? m) (get m :jolt/type)))
|
|
(error (string "Can't merge " (type m) " into a map"))
|
|
# other map-like tables (records, sorted-maps, host tables): lenient conj
|
|
(set result (core-conj result m))))
|
|
(++ i))
|
|
result)))
|
|
|
|
(defn core-merge-with [f & maps]
|
|
(if (phm? (first maps))
|
|
(do (var result (first maps)) (var mi 1) (while (< mi (length maps)) (let [m (maps mi)]
|
|
(each k (if (phm? m) (keys (phm-to-struct m)) (keys m)) (let [existing (phm-get result k)
|
|
val (if (phm? m) (phm-get m k) (m k))]
|
|
(set result (phm-assoc result k (if (nil? existing) val (f existing val)))))) (++ mi))) result)
|
|
(do (var result @{}) (each m maps (each k (if (phm? m) (keys (phm-to-struct m)) (keys m)) (let [existing (result k)] (put result k (if (nil? existing) (m k) (f existing (m k))))))) (table/to-struct result))))
|
|
|
|
(defn core-keys [m]
|
|
(if (phm? m) (tuple ;(keys (phm-to-struct m))) (tuple ;(keys m))))
|
|
|
|
(defn core-vals [m]
|
|
(if (phm? m) (do (def s (phm-to-struct m)) (tuple ;(map |(s $) (keys s)))) (tuple ;(map |(m $) (keys m)))))
|
|
|
|
(defn core-select-keys [m ks]
|
|
(var result @{})
|
|
(each k (realize-for-iteration ks)
|
|
(let [v (core-get m k)]
|
|
(if (not (nil? v)) (put result k v))))
|
|
(if (struct? m) (table/to-struct result) result))
|
|
|
|
(defn core-zipmap [ks vs]
|
|
(let [ks (realize-for-iteration ks) vs (realize-for-iteration vs)]
|
|
(var result @{})
|
|
(var i 0)
|
|
(while (and (< i (length ks)) (< i (length vs)))
|
|
(put result (in ks i) (in vs i))
|
|
(++ i))
|
|
(table/to-struct result)))
|
|
|
|
# ============================================================
|
|
# Transducers
|
|
# ============================================================
|
|
# A transducer is (fn [rf] rf') where rf' is a reducing fn with arities
|
|
# []=init, [acc]=complete, [acc x]=step. map/filter/take/... return a
|
|
# transducer when called with no collection.
|
|
|
|
(defn core-reduced [x] @{:jolt/type :jolt/reduced :val x})
|
|
(defn core-reduced? [x] (and (table? x) (= :jolt/reduced (x :jolt/type))))
|
|
(defn core-unreduced [x] (if (core-reduced? x) (x :val) x))
|
|
(defn- ensure-reduced [x] (if (core-reduced? x) x (core-reduced x)))
|
|
|
|
(defn td-map [f]
|
|
(fn [rf] (fn [& a] (case (length a) 0 (rf) 1 (rf (a 0)) (rf (a 0) (f (a 1)))))))
|
|
(defn td-filter [pred]
|
|
(fn [rf] (fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(if (truthy? (pred (a 1))) (rf (a 0) (a 1)) (a 0))))))
|
|
(defn td-remove [pred] (td-filter (fn [x] (not (pred x)))))
|
|
(defn td-keep [f]
|
|
(fn [rf] (fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(let [v (f (a 1))] (if (nil? v) (a 0) (rf (a 0) v)))))))
|
|
(defn td-take [n]
|
|
(fn [rf]
|
|
(var left n)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(if (<= left 0) (core-reduced (a 0))
|
|
(let [r (rf (a 0) (a 1))] (set left (dec left))
|
|
(if (<= left 0) (ensure-reduced r) r)))))))
|
|
(defn td-drop [n]
|
|
(fn [rf]
|
|
(var left n)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(if (> left 0) (do (set left (dec left)) (a 0)) (rf (a 0) (a 1)))))))
|
|
(defn td-take-while [pred]
|
|
(fn [rf]
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(if (truthy? (pred (a 1))) (rf (a 0) (a 1)) (core-reduced (a 0)))))))
|
|
(defn td-drop-while [pred]
|
|
(fn [rf]
|
|
(var dropping true)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(do (when (and dropping (not (truthy? (pred (a 1))))) (set dropping false))
|
|
(if dropping (a 0) (rf (a 0) (a 1))))))))
|
|
(defn td-map-indexed [f]
|
|
(fn [rf]
|
|
(var i -1)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0)) (do (++ i) (rf (a 0) (f i (a 1))))))))
|
|
|
|
(defn- reduce-with-reduced
|
|
"Reduce coll with reducing fn rf and seed init, honoring `reduced`. Steps lazy
|
|
seqs one cell at a time so a reducing fn that returns `reduced` (e.g. the
|
|
`take`/`take-while` transducers) can short-circuit over an INFINITE seq instead
|
|
of realizing it eagerly. Returns the final (unwrapped) accumulator."
|
|
[rf init coll]
|
|
(var acc init)
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var cur coll) (var go true)
|
|
(while go
|
|
(let [cell (realize-ls cur)]
|
|
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
|
|
(set go false)
|
|
(do
|
|
(set acc (rf acc (in cell 0)))
|
|
(if (core-reduced? acc)
|
|
(do (set acc (acc :val)) (set go false))
|
|
(let [rt (in cell 1)]
|
|
(if (nil? rt) (set go false) (set cur (make-lazy-seq rt))))))))))
|
|
(do
|
|
(var stop false)
|
|
(each x (if (set? coll) (phs-seq coll) (realize-for-iteration coll))
|
|
(when (not stop)
|
|
(set acc (rf acc x))
|
|
(when (core-reduced? acc) (set acc (acc :val)) (set stop true))))))
|
|
acc)
|
|
|
|
(defn- transduce-reduce
|
|
"Reduce coll with reducing fn rf and seed init, honoring `reduced`."
|
|
[rf init coll]
|
|
(reduce-with-reduced rf init coll))
|
|
|
|
(defn core-transduce
|
|
"(transduce xform f coll) or (transduce xform f init coll)."
|
|
[xform f & rest]
|
|
(let [has-init (= 2 (length rest))
|
|
init (if has-init (in rest 0) (f))
|
|
coll (if has-init (in rest 1) (in rest 0))
|
|
rf (xform f)]
|
|
(rf (transduce-reduce rf init coll))))
|
|
|
|
(defn core-into
|
|
"(into to from) or (into to xform from)."
|
|
[to & rest]
|
|
(if (= 2 (length rest))
|
|
(let [xform (in rest 0) from (in rest 1)]
|
|
(core-transduce xform (fn [& a] (case (length a) 0 to 1 (a 0) (core-conj (a 0) (a 1)))) to from))
|
|
(into-conj to (realize-for-iteration (in rest 0)))))
|
|
|
|
(defn core-sequence
|
|
"(sequence coll) or (sequence xform coll) — eager here (returns a seq/tuple)."
|
|
[a & rest]
|
|
(if (= 0 (length rest))
|
|
(core-seq a)
|
|
(tuple ;(core-transduce a (fn [& x] (case (length x) 0 @[] 1 (x 0) (do (array/push (x 0) (x 1)) (x 0)))) @[] (in rest 0)))))
|
|
|
|
(defn- seq-done?
|
|
"True when cursor c (a lazy-seq or a concrete collection) is exhausted.
|
|
Uses cell realization for lazy-seqs so nil elements don't end the seq early."
|
|
[c]
|
|
(if (lazy-seq? c)
|
|
(let [cell (realize-ls c)]
|
|
(or (nil? cell) (= :jolt/pending cell) (= 0 (length cell))))
|
|
(or (nil? c) (= 0 (length c)))))
|
|
|
|
(defn core-map [f & colls]
|
|
(def f (as-fn f))
|
|
(if (= 0 (length colls))
|
|
(td-map f) # transducer arity
|
|
(if (= 1 (length colls))
|
|
(let [coll (colls 0)]
|
|
(if (lazy-seq? coll)
|
|
# Lazy input: stay lazy so infinite/self-referential seqs work.
|
|
(do
|
|
(defn mstep [c]
|
|
(fn []
|
|
(if (seq-done? c) nil
|
|
@[(f (core-first c)) (mstep (core-rest c))])))
|
|
(make-lazy-seq (mstep coll)))
|
|
# Concrete collection: eager (preserves tuple/array representation).
|
|
(let [c (if (set? coll) (phs-seq coll) (realize-for-iteration coll))
|
|
result (do (var res @[]) (each x c (array/push res (f x))) res)]
|
|
(if (jvec? coll) (make-vec result) result))))
|
|
# Multi-collection: lazy-seq with per-element independent state
|
|
(let [init-cs (array/new-filled (length colls) nil)
|
|
init-idxs (array/new-filled (length colls) 0)
|
|
init-reals (array/new-filled (length colls) nil)
|
|
_ (do
|
|
(var i 0)
|
|
(while (< i (length colls))
|
|
(let [c (in colls i)]
|
|
(if (lazy-seq? c)
|
|
(put init-cs i c)
|
|
(do (put init-cs i nil)
|
|
(put init-reals i (if (set? c) (phs-seq c) (realize-for-iteration c))))))
|
|
(++ i))
|
|
nil)]
|
|
(defn step [cs idxs reals]
|
|
"cs: current lazy-seq cursors, idxs: indices, reals: realized colls"
|
|
(fn []
|
|
(var args @[])
|
|
(var next-cs (array/new-filled (length cs) nil))
|
|
(var next-idxs (array/new-filled (length idxs) 0))
|
|
(var next-reals (array/new-filled (length reals) nil))
|
|
(var ok true)
|
|
(var i 0)
|
|
(while (< i (length cs))
|
|
(let [cur (in cs i) ridx (in idxs i) real (in reals i)]
|
|
(if (not (nil? cur))
|
|
(let [val (ls-first cur)]
|
|
(if (nil? val) (do (set ok false) (break))
|
|
(do (array/push args val)
|
|
(put next-cs i (ls-rest cur))
|
|
(put next-idxs i (+ ridx 1))
|
|
(put next-reals i nil))))
|
|
(let [c (if (nil? real)
|
|
(let [rc (realize-for-iteration (in colls i))]
|
|
(put next-reals i rc) rc)
|
|
real)]
|
|
(if (>= ridx (length c)) (do (set ok false) (break))
|
|
(do (array/push args (in c ridx))
|
|
(put next-cs i nil)
|
|
(put next-idxs i (+ ridx 1))
|
|
(put next-reals i c))))))
|
|
(++ i))
|
|
(if (and ok (= (length args) (length cs)))
|
|
@[(apply f args) (step next-cs next-idxs next-reals)]
|
|
nil)))
|
|
(make-lazy-seq (step init-cs init-idxs init-reals))))))
|
|
|
|
(defn core-filter [pred & rest]
|
|
(def pred (as-fn pred))
|
|
(if (= 0 (length rest)) (td-filter pred)
|
|
(let [coll (in rest 0)]
|
|
(if (lazy-seq? coll)
|
|
# lazy input -> lazy output (supports infinite seqs)
|
|
(do
|
|
(defn fstep [c]
|
|
(fn []
|
|
(var cur c) (var hit nil) (var found false)
|
|
(while (and (not found) (not (seq-done? cur)))
|
|
(let [x (core-first cur)]
|
|
(if (pred x) (do (set hit @[x (core-rest cur)]) (set found true))
|
|
(set cur (core-rest cur)))))
|
|
(if found @[(in hit 0) (fstep (in hit 1))] nil)))
|
|
(make-lazy-seq (fstep coll)))
|
|
(do
|
|
(var result @[])
|
|
(each x (if (set? coll) (phs-seq coll) (realize-for-iteration coll))
|
|
(if (pred x) (array/push result x)))
|
|
(if (jvec? coll) (make-vec result) result))))))
|
|
|
|
(defn core-remove [pred & rest]
|
|
(def pred (as-fn pred))
|
|
(if (= 0 (length rest)) (td-remove pred)
|
|
(core-filter (fn [x] (not (pred x))) (in rest 0))))
|
|
|
|
(def core-reduce
|
|
(fn [& args]
|
|
(case (length args)
|
|
# 2-arg: seed is the first element; reduce over the rest. Lazy seqs are
|
|
# stepped incrementally (via reduce-with-reduced) so `reduced` can
|
|
# short-circuit an infinite seq rather than realizing it.
|
|
2 (let [f (args 0) coll (args 1)]
|
|
(if (lazy-seq? coll)
|
|
(let [cell (realize-ls coll)]
|
|
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
|
|
(f)
|
|
(let [rt (in cell 1)]
|
|
(if (nil? rt) (in cell 0)
|
|
(reduce-with-reduced f (in cell 0) (make-lazy-seq rt))))))
|
|
(let [c (if (set? coll) (phs-seq coll) (realize-for-iteration coll))]
|
|
(if (= 0 (length c)) (f)
|
|
(reduce-with-reduced f (in c 0) (array/slice c 1))))))
|
|
3 (let [f (args 0) val (args 1) coll (args 2)]
|
|
(reduce-with-reduced f val coll))
|
|
(error "Wrong number of args passed to: reduce"))))
|
|
|
|
(defn core-take [n & rest]
|
|
(if (= 0 (length rest)) (td-take n)
|
|
(let [coll (in rest 0)]
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var result @[])
|
|
(var cur coll)
|
|
(var i 0)
|
|
(while (and (< i n) (not (nil? (ls-first cur))))
|
|
(array/push result (ls-first cur))
|
|
(set cur (ls-rest cur))
|
|
(++ i))
|
|
result)
|
|
(let [c (realize-for-iteration coll)]
|
|
(var result @[])
|
|
(var i 0)
|
|
(while (and (< i n) (< i (length c)))
|
|
(array/push result (in c i))
|
|
(++ i))
|
|
(if (jvec? coll) (make-vec result) result))))))
|
|
|
|
(defn core-drop [n & rest]
|
|
(if (= 0 (length rest)) (td-drop n)
|
|
(let [coll (in rest 0)]
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var cur coll)
|
|
(var i 0)
|
|
(while (and (< i n) (ls-first cur))
|
|
(set cur (ls-rest cur))
|
|
(++ i))
|
|
(if (nil? (ls-first cur)) nil cur))
|
|
(let [c (realize-for-iteration coll)
|
|
dropped (array/slice c (min n (length c)))]
|
|
(if (jvec? coll) (make-vec dropped) dropped))))))
|
|
|
|
(defn core-second [coll] (core-first (core-rest coll)))
|
|
# ffirst / nfirst / fnext / nnext now live in the Clojure overlay (jolt-core/clojure/core.clj).
|
|
|
|
(defn core-last [coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(if (= 0 (length c)) nil (in c (- (length c) 1)))))
|
|
|
|
(defn core-drop-last [a & rest]
|
|
(let [n (if (= 0 (length rest)) 1 a)
|
|
coll (if (= 0 (length rest)) a (in rest 0))
|
|
c (realize-for-iteration coll)
|
|
end (max 0 (- (length c) n))]
|
|
(tuple ;(array/slice c 0 end))))
|
|
|
|
(defn core-take-last [n coll]
|
|
(let [c (realize-for-iteration coll)
|
|
start (max 0 (- (length c) n))]
|
|
(if (= 0 (length c)) nil (tuple ;(array/slice c start)))))
|
|
|
|
(defn core-take-while [pred & rest]
|
|
(def pred (as-fn pred))
|
|
(if (= 0 (length rest)) (td-take-while pred)
|
|
(let [coll (in rest 0)]
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var result @[]) (var cur coll) (var go true)
|
|
(while (and go (not (seq-done? cur)))
|
|
(let [x (core-first cur)]
|
|
(if (pred x) (do (array/push result x) (set cur (core-rest cur)))
|
|
(set go false))))
|
|
result)
|
|
(do
|
|
(var result @[])
|
|
(each x (realize-for-iteration coll) (if (pred x) (array/push result x) (break)))
|
|
(if (jvec? coll) (make-vec result) result))))))
|
|
|
|
(defn core-drop-while [pred & rest]
|
|
(def pred (as-fn pred))
|
|
(if (= 0 (length rest)) (td-drop-while pred)
|
|
(let [coll (in rest 0)
|
|
c (realize-for-iteration coll)]
|
|
(var start 0)
|
|
(while (and (< start (length c)) (pred (c start)))
|
|
(++ start))
|
|
(if (tuple? c)
|
|
(tuple/slice c start)
|
|
(array/slice c start)))))
|
|
|
|
(defn coll->cells [c]
|
|
"Convert a seqable to lazy-seq cell chain: nil or [first, rest-thunk].
|
|
If the value is a function, call it and use the result.
|
|
If the result is already a cell (array of [val, function]), return it directly."
|
|
(if (nil? c) nil
|
|
(if (pvec? c) (coll->cells (pv->array c))
|
|
(if (plist? c) (coll->cells (pl->array c))
|
|
(if (function? c)
|
|
(let [r (c)]
|
|
(if (and (indexed? r) (= 2 (length r)) (function? (in r 1)))
|
|
r
|
|
(coll->cells r)))
|
|
(if (lazy-seq? c)
|
|
(let [cell (realize-ls c)]
|
|
(if (= :jolt/pending cell) nil cell))
|
|
(if (indexed? c)
|
|
(if (= 0 (length c)) nil
|
|
(if (and (= 2 (length c)) (function? (in c 1)))
|
|
c # already a cell [val, rest-thunk]
|
|
(let [f (in c 0)
|
|
rest (if (> (length c) 1)
|
|
(if (tuple? c) (tuple/slice c 1) (array/slice c 1))
|
|
nil)]
|
|
@[f (fn [] (coll->cells rest))])))
|
|
nil)))))))
|
|
|
|
(defn core-concat [& colls]
|
|
"Truly lazy concatenation. `step` returns a 0-arg thunk that is only forced
|
|
when the consumer asks for the next cell, so nothing in `colls` is realized at
|
|
construction time. This is essential for self-referential lazy seqs (e.g.
|
|
(def fib (lazy-cat [0 1] (map + (rest fib) fib)))): the later colls must not be
|
|
forced until after the surrounding `def` has bound the var."
|
|
(defn step [cs]
|
|
(fn []
|
|
(if (= 0 (length cs))
|
|
nil
|
|
(let [c (in cs 0)
|
|
remaining (array/slice cs 1)
|
|
cell (coll->cells c)]
|
|
(if (nil? cell)
|
|
# current coll is empty: advance to the next one
|
|
((step remaining))
|
|
(let [val (in cell 0)
|
|
rest-fn (in cell 1)]
|
|
@[val (step (if (nil? rest-fn)
|
|
remaining
|
|
(array/insert remaining 0 rest-fn)))]))))))
|
|
(make-lazy-seq (step (if (tuple? colls) (array/slice colls) colls))))
|
|
|
|
(defn core-mapcat
|
|
"(mapcat f & colls) — map then concat. (mapcat f) returns a transducer."
|
|
[f & colls]
|
|
(if (= 0 (length colls))
|
|
# transducer: map f over each input, then splice (cat) the result
|
|
(fn [rf]
|
|
(fn [& a]
|
|
(case (length a)
|
|
0 (rf)
|
|
1 (rf (a 0))
|
|
(do (var acc (a 0))
|
|
(each x (realize-for-iteration (f (a 1)))
|
|
(set acc (rf acc x)))
|
|
acc))))
|
|
# map, then concat; a non-seqable result counts as a single element (this
|
|
# leniency is what jolt's `for` expansion relies on for :let on the last
|
|
# binding, whose body yields a scalar rather than a seq).
|
|
(let [mapped (realize-for-iteration (core-apply core-map f colls))
|
|
seqs (map (fn [item]
|
|
(if (or (tuple? item) (array? item) (pvec? item)
|
|
(lazy-seq? item) (set? item))
|
|
item (tuple item)))
|
|
mapped)]
|
|
(core-apply core-concat seqs))))
|
|
|
|
(defn core-reverse [coll]
|
|
(if (nil? coll) @[]
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var result @[])
|
|
(var cur coll)
|
|
(while (not (nil? (ls-first cur)))
|
|
(array/push result (ls-first cur))
|
|
(set cur (ls-rest cur)))
|
|
(var reversed @[])
|
|
(var i (dec (length result)))
|
|
(while (>= i 0)
|
|
(array/push reversed (in result i))
|
|
(-- i))
|
|
reversed)
|
|
(let [c (realize-for-iteration coll)]
|
|
(var result @[])
|
|
(var i (dec (length c)))
|
|
(while (>= i 0)
|
|
(array/push result (in c i))
|
|
(-- i))
|
|
result))))
|
|
|
|
(defn core-nth
|
|
"Return the nth element of a sequential collection. With a not-found arg, return
|
|
it when idx is out of bounds (even if it's nil); without one, throw — matching
|
|
Clojure, where (nth coll i nil) returns nil rather than throwing."
|
|
[coll idx & rest]
|
|
(def has-default (> (length rest) 0))
|
|
(def default (if has-default (in rest 0) nil))
|
|
(defn oob [n] (if has-default default (error (string "Index " idx " out of bounds, length: " n))))
|
|
(if (nil? coll) default # (nth nil i) -> nil / default, never throws
|
|
(if (core-transient? coll)
|
|
(let [a (coll :arr)] (if (and (>= idx 0) (< idx (length a))) (in a idx) (oob (length a))))
|
|
(if (plist? coll)
|
|
(let [a (pl->array coll)]
|
|
(if (and (>= idx 0) (< idx (length a))) (in a idx) (oob (length a))))
|
|
(if (pvec? coll)
|
|
(if (and (>= idx 0) (< idx (pv-count coll)))
|
|
(pv-nth coll idx)
|
|
(oob (pv-count coll)))
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var cur coll)
|
|
(var i 0)
|
|
(while (and (< i idx) (ls-first cur))
|
|
(set cur (ls-rest cur))
|
|
(++ i))
|
|
(if (ls-first cur) (ls-first cur) (oob idx)))
|
|
(do
|
|
(var c (realize-for-iteration coll))
|
|
(if (and (>= idx 0) (< idx (length c)))
|
|
(if (string? c) (make-char (in c idx)) (in c idx))
|
|
(oob (length c))))))))))
|
|
|
|
(defn core-sort
|
|
"(sort coll) or (sort comparator coll). Comparator may return a boolean or a
|
|
Clojure-style negative/zero/positive number."
|
|
[a & rest]
|
|
(let [has-cmp (> (length rest) 0)
|
|
cmp (if has-cmp a nil)
|
|
coll (if has-cmp (first rest) a)]
|
|
(if (nil? coll) @[]
|
|
(let [arr (array/slice (realize-for-iteration coll))]
|
|
(if has-cmp
|
|
(sort arr (fn [x y] (let [r (cmp x y)] (if (number? r) (< r 0) (truthy? r)))))
|
|
(sort arr))
|
|
(tuple/slice (tuple ;arr))))))
|
|
|
|
# (sort-by keyfn coll) or (sort-by keyfn comparator coll). The comparator (when
|
|
# given) compares the KEYS and may return a boolean or a Clojure-style number.
|
|
(defn core-sort-by [keyfn & rest]
|
|
(def keyfn (as-fn keyfn))
|
|
(let [has-cmp (> (length rest) 1)
|
|
coll (if has-cmp (in rest 1) (first rest))]
|
|
(if (nil? coll) (tuple)
|
|
(let [c (realize-for-iteration coll)
|
|
arr (if (tuple? c) (array/slice c) (array/slice c))]
|
|
(if has-cmp
|
|
(let [cmp (first rest)]
|
|
(sort arr (fn [x y] (let [r (cmp (keyfn x) (keyfn y))]
|
|
(if (number? r) (< r 0) (truthy? r))))))
|
|
(sort-by keyfn arr))
|
|
(tuple/slice (tuple ;arr))))))
|
|
|
|
(defn core-distinct [coll]
|
|
(if (nil? coll) @[]
|
|
(if (lazy-seq? coll)
|
|
(do
|
|
(var seen @{})
|
|
(var result @[])
|
|
(var cur coll)
|
|
(while (not (nil? (ls-first cur)))
|
|
(let [x (ls-first cur)]
|
|
(if (nil? (seen x))
|
|
(do (put seen x true) (array/push result x))))
|
|
(set cur (ls-rest cur)))
|
|
result)
|
|
(do
|
|
(var seen @{})
|
|
(var result @[])
|
|
(each x (realize-for-iteration coll)
|
|
(if (nil? (seen x))
|
|
(do (put seen x true) (array/push result x))))
|
|
(if (jvec? coll) (make-vec result) result)))))
|
|
|
|
(defn core-group-by [f coll]
|
|
(def f (as-fn f))
|
|
# phm base so collection keys group by value
|
|
(var result (make-phm))
|
|
(each x (realize-for-iteration coll)
|
|
(let [k (f x)]
|
|
(set result (phm-assoc result k (array/push (phm-get result k @[]) x)))))
|
|
result)
|
|
|
|
(defn core-frequencies [coll]
|
|
# phm base so collection elements are counted by value
|
|
(var result (make-phm))
|
|
(each x (realize-for-iteration coll)
|
|
(set result (phm-assoc result x (+ 1 (phm-get result x 0)))))
|
|
result)
|
|
|
|
(defn core-partition
|
|
"(partition n coll) or (partition n step coll). Only complete partitions of
|
|
size n are kept (use partition-all to keep the trailing remainder)."
|
|
[n & rest]
|
|
(let [has-step (> (length rest) 1)
|
|
step (if has-step (first rest) n)
|
|
coll (realize-for-iteration (if has-step (in rest 1) (first rest)))]
|
|
(var result @[]) (var i 0)
|
|
(while (<= (+ i n) (length coll))
|
|
(var part @[]) (var j 0)
|
|
(while (< j n) (array/push part (in coll (+ i j))) (++ j))
|
|
(array/push result (tuple/slice (tuple ;part)))
|
|
(+= i step))
|
|
result))
|
|
|
|
(defn core-partition-by [f coll]
|
|
(def f (as-fn f))
|
|
(var result @[])
|
|
(var part @[])
|
|
(var last-k nil)
|
|
(each x (realize-for-iteration coll)
|
|
(let [k (f x)]
|
|
(if (and last-k (deep= k last-k))
|
|
(array/push part x)
|
|
(do
|
|
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
|
|
(set part @[x])
|
|
(set last-k k)))))
|
|
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
|
|
result)
|
|
|
|
(defn core-partition-all [n coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(var result @[]) (var i 0)
|
|
(while (< i (length c))
|
|
(var part @[]) (var j 0)
|
|
(while (and (< j n) (< (+ i j) (length c)))
|
|
(array/push part (in c (+ i j))) (++ j))
|
|
(array/push result (tuple/slice (tuple ;part)))
|
|
(+= i n))
|
|
result))
|
|
|
|
(defn core-reductions
|
|
"(reductions f coll) or (reductions f init coll) -> seq of intermediate accs."
|
|
[f init-or-coll &opt maybe-coll]
|
|
(let [has-init (not (nil? maybe-coll))
|
|
coll (realize-for-iteration (if has-init maybe-coll init-or-coll))
|
|
result @[]]
|
|
(if has-init
|
|
(do (var acc init-or-coll) (array/push result acc)
|
|
(each x coll (set acc (f acc x)) (array/push result acc)))
|
|
(when (> (length coll) 0)
|
|
(var acc (in coll 0)) (array/push result acc)
|
|
(var i 1)
|
|
(while (< i (length coll)) (set acc (f acc (in coll i))) (array/push result acc) (++ i))))
|
|
(tuple/slice (tuple ;result))))
|
|
|
|
(defn core-dedupe [coll]
|
|
(let [c (realize-for-iteration coll) result @[]]
|
|
(var prev :jolt/none)
|
|
(each x c
|
|
(when (or (= prev :jolt/none) (not (deep= x prev)))
|
|
(array/push result x))
|
|
(set prev x))
|
|
(tuple/slice (tuple ;result))))
|
|
|
|
(defn core-keep-indexed [f coll]
|
|
(let [c (realize-for-iteration coll) result @[]]
|
|
(var i 0)
|
|
(each x c (let [v (f i x)] (when (not (nil? v)) (array/push result v))) (++ i))
|
|
(tuple/slice (tuple ;result))))
|
|
|
|
(defn core-map-indexed [f & rest]
|
|
(if (= 0 (length rest)) (td-map-indexed f)
|
|
(let [c (realize-for-iteration (in rest 0)) result @[]]
|
|
(var i 0)
|
|
(each x c (array/push result (f i x)) (++ i))
|
|
(tuple/slice (tuple ;result)))))
|
|
|
|
(defn core-cycle [coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(if (= 0 (length c))
|
|
(make-lazy-seq (fn [] nil))
|
|
(do
|
|
(defn cstep [i] (fn [] @[(in c (% i (length c))) (cstep (+ i 1))]))
|
|
(make-lazy-seq (cstep 0))))))
|
|
|
|
(defn core-reduce-kv [f init m]
|
|
(var acc init)
|
|
(cond
|
|
(phm? m) (each k (keys (phm-to-struct m)) (set acc (f acc k (phm-get m k))))
|
|
(or (struct? m) (table? m)) (each k (keys m) (set acc (f acc k (get m k))))
|
|
(indexed? m) (do (var i 0) (each x m (set acc (f acc i x)) (++ i))))
|
|
acc)
|
|
|
|
# peek/pop are defined only on stacks (vectors -> last end, lists -> front);
|
|
# Clojure throws on sets/maps/seqs/strings/scalars.
|
|
(defn core-peek [coll]
|
|
(cond
|
|
(nil? coll) nil
|
|
(plist? coll) (if (pl-empty? coll) nil (pl-first coll)) # list: first
|
|
(pvec? coll) (if (= 0 (pv-count coll)) nil (pv-nth coll (- (pv-count coll) 1))) # vector: last
|
|
(tuple? coll) (if (= 0 (length coll)) nil (in coll (- (length coll) 1))) # vector: last
|
|
(array? coll) (if (= 0 (length coll)) nil (in coll 0)) # list: first
|
|
(error (string "peek not supported on " (type coll)))))
|
|
|
|
(defn core-pop [coll]
|
|
(cond
|
|
(nil? coll) nil
|
|
(plist? coll) (if (pl-empty? coll) (error "Can't pop empty list") (pl-rest coll))
|
|
(pvec? coll) (if (= 0 (pv-count coll)) (error "Can't pop empty vector") (pv-pop coll))
|
|
(tuple? coll) (if (= 0 (length coll)) (error "Can't pop empty vector") (tuple/slice coll 0 (- (length coll) 1)))
|
|
(array? coll) (if (= 0 (length coll)) (error "Can't pop empty list") (array/slice coll 1))
|
|
(error (string "pop not supported on " (type coll)))))
|
|
|
|
# Clojure coerces subvec indices with (int ...): floats truncate and NaN -> 0;
|
|
# only non-numbers and out-of-range values throw.
|
|
(defn- subvec-idx [x]
|
|
(cond
|
|
(not (number? x)) (error "subvec index must be a number")
|
|
(not= x x) 0 # NaN -> 0
|
|
(math/trunc x)))
|
|
(defn core-subvec [v start &opt end]
|
|
(when (not (or (pvec? v) (tuple? v) (array? v)))
|
|
(error (string "subvec requires a vector, got " (type v))))
|
|
(let [a (vview v)
|
|
s (subvec-idx start)
|
|
e (if (nil? end) (length a) (subvec-idx end))]
|
|
(when (not (and (>= s 0) (<= s e) (<= e (length a))))
|
|
(error (string "subvec indices out of range: " s " " e " (length " (length a) ")")))
|
|
(make-vec (tuple/slice a s e))))
|
|
|
|
(defn core-trampoline [f & args]
|
|
(var result (apply f args))
|
|
(while (function? result) (set result (result)))
|
|
result)
|
|
|
|
(def core-format (fn [fmt & args] (string/format fmt ;args)))
|
|
|
|
# ============================================================
|
|
# Sequence generators
|
|
# ============================================================
|
|
|
|
(def core-range
|
|
(fn [& args]
|
|
(if (= 0 (length args))
|
|
# (range) — infinite lazy sequence 0, 1, 2, ...
|
|
(do
|
|
(defn rstep [i] (fn [] @[i (rstep (+ i 1))]))
|
|
(make-lazy-seq (rstep 0)))
|
|
(let [start (if (> (length args) 1) (args 0) 0)
|
|
end (if (> (length args) 1) (args 1) (args 0))
|
|
step (if (> (length args) 2) (args 2) 1)]
|
|
(var result @[])
|
|
(var i start)
|
|
(while (if (pos? step) (< i end) (> i end))
|
|
(array/push result i)
|
|
(+= i step))
|
|
(tuple/slice (tuple ;result))))))
|
|
|
|
(defn core-repeat
|
|
"(repeat x) -> infinite lazy seq of x; (repeat n x) -> n copies of x."
|
|
[a & rest]
|
|
(if (= 0 (length rest))
|
|
(do (defn rstep [] (fn [] @[a (rstep)])) (make-lazy-seq (rstep)))
|
|
(let [n a x (in rest 0)]
|
|
(var result @[]) (var i 0)
|
|
(while (< i n) (array/push result x) (++ i))
|
|
result)))
|
|
|
|
(defn core-iterate [f x]
|
|
"Lazy infinite sequence x, (f x), (f (f x)), ..."
|
|
(defn istep [v] (fn [] @[v (istep (f v))]))
|
|
(make-lazy-seq (istep x)))
|
|
|
|
(defn core-repeatedly
|
|
"(repeatedly f) -> infinite lazy seq of (f) calls; (repeatedly n f) -> n calls."
|
|
[a & rest]
|
|
(if (= 0 (length rest))
|
|
(do (defn rstep [] (fn [] @[(a) (rstep)])) (make-lazy-seq (rstep)))
|
|
(let [n a f (in rest 0)]
|
|
(var result @[]) (var i 0)
|
|
(while (< i n) (array/push result (f)) (++ i))
|
|
result)))
|
|
|
|
# ============================================================
|
|
# Higher-order functions
|
|
# ============================================================
|
|
|
|
(def core-identity (fn [x] x))
|
|
|
|
(def core-constantly (fn [x] (fn [& _] x)))
|
|
|
|
(defn core-complement [f]
|
|
(fn [& args] (not (apply f args))))
|
|
|
|
(defn core-qualified-symbol? [x]
|
|
"Returns true if x is a symbol with a namespace."
|
|
(and (struct? x) (= :symbol (x :jolt/type)) (not (nil? (x :ns)))))
|
|
|
|
(defn core-simple-symbol? [x]
|
|
(and (struct? x) (= :symbol (x :jolt/type)) (nil? (x :ns))))
|
|
(defn core-qualified-keyword? [x]
|
|
(and (keyword? x) (not (nil? (string/find "/" (string x))))))
|
|
(defn core-simple-keyword? [x]
|
|
(and (keyword? x) (nil? (string/find "/" (string x)))))
|
|
(defn core-ident? [x] (or (core-keyword? x) (core-symbol? x)))
|
|
(defn core-qualified-ident? [x]
|
|
(or (core-qualified-symbol? x) (core-qualified-keyword? x)))
|
|
(defn core-simple-ident? [x]
|
|
(or (core-simple-symbol? x) (core-simple-keyword? x)))
|
|
# Jolt has no inst/uri/uuid host types, so these are always false; inst-ms has
|
|
# nothing valid to read.
|
|
(defn core-inst? [x] false)
|
|
(defn core-inst-ms [x] (error "Not an instant (no inst type in Jolt)"))
|
|
(defn core-uri? [x] false)
|
|
(defn core-uuid? [x] false)
|
|
(defn core-bytes? [x] (buffer? x))
|
|
(defn core-tagged-literal? [x]
|
|
(and (table? x) (= :jolt/tagged-literal (get x :jolt/type))))
|
|
|
|
(defn core-meta [x]
|
|
"Returns the metadata of x, or nil."
|
|
(cond
|
|
(var? x) (var-meta x)
|
|
# symbols carry reader metadata (type hints etc.) in a :meta field
|
|
(and (struct? x) (= :symbol (get x :jolt/type))) (get x :meta)
|
|
(table? x) (or (get x :jolt/meta) (get x :meta))
|
|
nil))
|
|
|
|
(defn core-every-pred [& preds]
|
|
(fn [& xs]
|
|
(var ok true)
|
|
(each p preds (each x xs (when (not (truthy? (p x))) (set ok false))))
|
|
ok))
|
|
|
|
(def core-comp
|
|
(fn [& fs]
|
|
(case (length fs)
|
|
0 identity
|
|
1 (fs 0)
|
|
2 (let [f (fs 0) g (fs 1)] (fn [& args] (f (apply g args))))
|
|
(let [f (last fs)
|
|
gs (array/slice fs 0 (dec (length fs)))]
|
|
(fn [& args]
|
|
(var result (apply (last gs) args))
|
|
(var i (- (length gs) 2))
|
|
(while (>= i 0)
|
|
(set result ((gs i) result))
|
|
(-- i))
|
|
(f result))))))
|
|
|
|
(defn core-partial [f & args]
|
|
(fn [& more] (apply f (array/concat (array/slice args) more))))
|
|
|
|
(defn core-juxt [& fs]
|
|
(fn [& args]
|
|
(tuple ;(map |(apply $ args) fs))))
|
|
|
|
(defn core-memoize [f]
|
|
(var cache @{})
|
|
(fn [& args]
|
|
(let [key (tuple ;args)]
|
|
(if-let [v (get cache key)]
|
|
v
|
|
(let [result (apply f args)]
|
|
(put cache key result)
|
|
result)))))
|
|
|
|
# ============================================================
|
|
# Collection constructors
|
|
# ============================================================
|
|
|
|
(defn core-vector [& xs] (make-vec xs))
|
|
(defn core-hash-map [& kvs] (make-phm kvs))
|
|
|
|
(defn core-array-map [& kvs]
|
|
(var result @{})
|
|
(var i 0)
|
|
(while (< i (length kvs))
|
|
(put result (kvs i) (kvs (+ i 1)))
|
|
(+= i 2))
|
|
(table/to-struct result))
|
|
|
|
(defn core-hash-set [& xs]
|
|
(apply make-phs xs))
|
|
|
|
(defn core-set? [x] (set? x))
|
|
(defn core-disj [s & ks]
|
|
(if (set? s) (apply phs-disj s ks) (error "disj expects a set")))
|
|
|
|
(defn core-lazy-seq [& body]
|
|
@[{:jolt/type :symbol :ns nil :name "make-lazy-seq"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn*"} []
|
|
@[{:jolt/type :symbol :ns nil :name "coll->cells"}
|
|
@[{:jolt/type :symbol :ns nil :name "do"} ;body]]]])
|
|
|
|
(defn core-lazy-cat [& colls]
|
|
"Macro: (lazy-cat & colls) — concatenate lazy sequences, wrapping each coll in lazy-seq.
|
|
concat is now lazy, so no outer make-lazy-seq wrapping is needed."
|
|
(def concat-form @[])
|
|
(array/push concat-form {:jolt/type :symbol :ns nil :name "concat"})
|
|
(each c colls
|
|
(array/push concat-form @[{:jolt/type :symbol :ns nil :name "lazy-seq"} c]))
|
|
concat-form)
|
|
|
|
(defn core-set [coll]
|
|
(apply core-hash-set (realize-for-iteration coll)))
|
|
|
|
(defn core-list [& xs]
|
|
(array ;xs))
|
|
|
|
# ============================================================
|
|
# String functions
|
|
# ============================================================
|
|
|
|
# Readable rendering of a value (Clojure pr semantics): strings quoted,
|
|
# keywords with leading ':', symbols by name, collections with their reader
|
|
# syntax. Used by both pr-str (readable) and str (collection elements).
|
|
(var pr-render nil)
|
|
|
|
# Format a number the way Clojure prints it: infinity and NaN have named forms
|
|
# (Janet renders them "inf"/"-inf"/"nan").
|
|
(defn- fmt-number [v]
|
|
(cond
|
|
(not (number? v)) (string v)
|
|
(= v math/inf) "Infinity"
|
|
(= v (- math/inf)) "-Infinity"
|
|
(not= v v) "NaN"
|
|
(string v)))
|
|
|
|
(defn- pr-render-seq [buf items open close]
|
|
(buffer/push-string buf open)
|
|
(var first true)
|
|
(each x items
|
|
(if first (set first false) (buffer/push-string buf " "))
|
|
(pr-render buf x))
|
|
(buffer/push-string buf close))
|
|
|
|
(defn- pr-render-pairs [buf pairs]
|
|
(buffer/push-string buf "{")
|
|
(var first true)
|
|
(each pair pairs
|
|
(if first (set first false) (buffer/push-string buf ", "))
|
|
(pr-render buf (in pair 0))
|
|
(buffer/push-string buf " ")
|
|
(pr-render buf (in pair 1)))
|
|
(buffer/push-string buf "}"))
|
|
|
|
(defn- name-of
|
|
"Extract a plain name string from a string, symbol struct, or a namespace/var
|
|
table (reading its :name) — never recurses into the cyclic ns structure."
|
|
[x]
|
|
(cond
|
|
(nil? x) nil
|
|
(string? x) x
|
|
(and (struct? x) (= :symbol (get x :jolt/type))) (x :name)
|
|
(or (struct? x) (table? x)) (name-of (get x :name))
|
|
(string x)))
|
|
|
|
(defn- var-display
|
|
"Render a Jolt var as #'ns/name. A var's :meta/:ns refs are cyclic, so this
|
|
reads only its :name and :ns name — printing the var's pairs would loop."
|
|
[v]
|
|
(let [nm (name-of (v :name))
|
|
ns (name-of (v :ns))]
|
|
(if ns (string "#'" ns "/" nm) (string "#'" nm))))
|
|
|
|
(set pr-render
|
|
(fn [buf v]
|
|
(cond
|
|
(nil? v) (buffer/push-string buf "nil")
|
|
(= true v) (buffer/push-string buf "true")
|
|
(= false v) (buffer/push-string buf "false")
|
|
(string? v) (do (buffer/push-string buf "\"") (buffer/push-string buf v) (buffer/push-string buf "\""))
|
|
(buffer? v) (do (buffer/push-string buf "\"") (buffer/push-string buf (string v)) (buffer/push-string buf "\""))
|
|
(keyword? v) (do (buffer/push-string buf ":") (buffer/push-string buf (string v)))
|
|
(core-char? v) (do (buffer/push-string buf "\\")
|
|
(buffer/push-string buf
|
|
(case (v :ch)
|
|
10 "newline" 32 "space" 9 "tab" 13 "return"
|
|
12 "formfeed" 8 "backspace" 0 "nul"
|
|
(char->string v))))
|
|
(regex? v) (do (buffer/push-string buf "#\"") (buffer/push-string buf (v :source)) (buffer/push-string buf "\""))
|
|
(number? v) (buffer/push-string buf (fmt-number v))
|
|
(and (struct? v) (= :symbol (v :jolt/type)))
|
|
(buffer/push-string buf (if (v :ns) (string (v :ns) "/" (v :name)) (v :name)))
|
|
(and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v))
|
|
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
|
|
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
|
|
(lazy-seq? v) (pr-render-seq buf (realize-for-iteration v) "(" ")")
|
|
(set? v) (pr-render-seq buf (phs-seq v) "#{" "}")
|
|
(phm? v) (pr-render-pairs buf (phm-entries v))
|
|
(core-transient? v) (buffer/push-string buf (string "#<transient " (v :kind) ">"))
|
|
(and (table? v) (= :jolt/chan (get v :jolt/type))) (buffer/push-string buf "#<channel>")
|
|
(pvec? v) (pr-render-seq buf (pv->array v) "[" "]")
|
|
(plist? v) (pr-render-seq buf (pl->array v) "(" ")")
|
|
(and (table? v) (get v :jolt/deftype)) (buffer/push-string buf (string v))
|
|
(tuple? v) (pr-render-seq buf v "[" "]")
|
|
# mutable mode: arrays are vectors -> print with [] (else lists -> ())
|
|
(array? v) (if mutable? (pr-render-seq buf v "[" "]") (pr-render-seq buf v "(" ")"))
|
|
(struct? v) (pr-render-pairs buf (pairs v))
|
|
(table? v) (pr-render-pairs buf (pairs v))
|
|
true (buffer/push-string buf (string v)))))
|
|
|
|
(defn- str-render-one
|
|
"Render one value with Clojure's `str`/.toString semantics (bare strings,
|
|
nil -> empty, keywords/symbols by name, collections via pr-render)."
|
|
[v]
|
|
(cond
|
|
(nil? v) ""
|
|
(string? v) v
|
|
(buffer? v) (string v)
|
|
(core-char? v) (char->string v)
|
|
(keyword? v) (string ":" (string v))
|
|
(and (struct? v) (= :symbol (v :jolt/type)))
|
|
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
|
|
(and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v)
|
|
(number? v) (fmt-number v)
|
|
(= true v) "true"
|
|
(= false v) "false"
|
|
(let [buf @""] (pr-render buf v) (string buf))))
|
|
|
|
(defn core-str [& xs]
|
|
(if (= 0 (length xs)) ""
|
|
(do
|
|
(var result @[])
|
|
(each x xs (array/push result (str-render-one x)))
|
|
(string/join result ""))))
|
|
|
|
(defn core-str-join
|
|
"clojure.string/join: stringify each element (Clojure semantics), then join."
|
|
[coll &opt sep]
|
|
(default sep "")
|
|
(let [items (realize-for-iteration coll)
|
|
parts @[]]
|
|
(each x items (array/push parts (str-render-one x)))
|
|
(string/join parts (str-render-one sep))))
|
|
|
|
(defn core-name
|
|
"Returns the name string of a keyword, symbol, or string (without namespace)."
|
|
[x]
|
|
(cond
|
|
(keyword? x) (let [s (string x) i (string/find "/" s)] (if i (string/slice s (+ i 1)) s))
|
|
(and (struct? x) (= :symbol (x :jolt/type))) (x :name)
|
|
(string? x) x
|
|
""))
|
|
|
|
(defn core-namespace
|
|
"Returns the namespace string of a keyword/symbol, or nil if none."
|
|
[x]
|
|
(cond
|
|
(keyword? x) (let [s (string x) i (string/find "/" s)] (if i (string/slice s 0 i) nil))
|
|
(and (struct? x) (= :symbol (x :jolt/type)))
|
|
(if (x :ns) (if (struct? (x :ns)) ((x :ns) :name) (string (x :ns))) nil)
|
|
nil))
|
|
|
|
(def core-subs
|
|
(fn [& args]
|
|
(when (not (or (= 2 (length args)) (= 3 (length args))))
|
|
(error "Wrong number of args passed to: subs"))
|
|
(let [s (args 0)
|
|
start (get args 1)]
|
|
(when (not (string? s)) (error (string "subs requires a string, got " (type s))))
|
|
(let [len (length s)
|
|
end (if (= 3 (length args)) (args 2) len)]
|
|
# Clojure validates bounds (no negative/from-end/clamping like Janet):
|
|
# 0 <= start <= end <= (count s).
|
|
(when (not (and (number? start) (number? end)
|
|
(= start (math/floor start)) (= end (math/floor end))
|
|
(>= start 0) (<= start end) (<= end len)))
|
|
(error "String index out of range"))
|
|
(string/slice s start end)))))
|
|
|
|
# ============================================================
|
|
# I/O — minimal wrappers
|
|
# ============================================================
|
|
|
|
# print/println use str semantics (bare strings); pr/prn use readable (quoted).
|
|
# All space-separate their args, like Clojure.
|
|
(defn core-print [& xs]
|
|
(var i 0)
|
|
(while (< i (length xs))
|
|
(if (> i 0) (prin " "))
|
|
(prin (str-render-one (xs i)))
|
|
(++ i))
|
|
nil)
|
|
|
|
(defn core-println [& xs]
|
|
(apply core-print xs)
|
|
(prin "\n")
|
|
nil)
|
|
|
|
(defn core-pr [& xs]
|
|
(var i 0)
|
|
(while (< i (length xs))
|
|
(if (> i 0) (prin " "))
|
|
(let [b @""] (pr-render b (xs i)) (prin (string b)))
|
|
(++ i))
|
|
nil)
|
|
|
|
(defn core-prn [& xs]
|
|
(apply core-pr xs)
|
|
(prin "\n")
|
|
nil)
|
|
|
|
(defn core-pr-str [& xs]
|
|
(def buf @"")
|
|
(var i 0)
|
|
(let [n (length xs)]
|
|
(while (< i n)
|
|
(pr-render buf (xs i))
|
|
(when (< (+ i 1) n) (buffer/push-string buf " "))
|
|
(++ i)))
|
|
(string buf))
|
|
|
|
# ============================================================
|
|
# Java-style arrays — backed by Janet's C primitives. Byte arrays use Janet
|
|
# buffers (contiguous, O(1) indexed get/put — genuinely fast); object and
|
|
# numeric arrays use Janet arrays. aget/aset/alength/aclone work over both.
|
|
# ============================================================
|
|
|
|
(defn core-alength [arr] (length arr))
|
|
|
|
(defn core-aget [arr & idxs]
|
|
# multi-dim: aget arr i j ... walks nested arrays
|
|
(var v arr) (each i idxs (set v (in v i))) v)
|
|
|
|
(defn core-aset [arr & more]
|
|
# (aset arr i v) or (aset arr i j ... v): last arg is the value
|
|
(let [n (length more) val (in more (- n 1))]
|
|
(var target arr) (var k 0)
|
|
(while (< k (- n 2)) (set target (in target (in more k))) (++ k))
|
|
(put target (in more (- n 2)) val) val))
|
|
|
|
(defn core-aclone [arr]
|
|
(if (buffer? arr) (buffer/slice arr) (array/slice arr)))
|
|
|
|
# Numeric / object arrays: (T-array size) | (T-array size init) | (T-array seq)
|
|
(defn- make-num-array [a rest init]
|
|
(if (number? a)
|
|
(array/new-filled a (if (> (length rest) 0) (in rest 0) init))
|
|
(array ;(realize-for-iteration a))))
|
|
(defn core-object-array [a & rest] (make-num-array a rest nil))
|
|
(defn core-int-array [a & rest] (make-num-array a rest 0))
|
|
(defn core-long-array [a & rest] (make-num-array a rest 0))
|
|
(defn core-short-array [a & rest] (make-num-array a rest 0))
|
|
(defn core-double-array [a & rest] (make-num-array a rest 0))
|
|
(defn core-float-array [a & rest] (make-num-array a rest 0))
|
|
(defn core-char-array [a & rest] (make-num-array a rest (make-char 0)))
|
|
(defn core-boolean-array [a & rest] (make-num-array a rest false))
|
|
|
|
# Byte arrays — Janet buffers (each element a 0..255 byte).
|
|
(defn core-byte-array [a & rest]
|
|
(if (number? a)
|
|
(buffer/new-filled a (band (if (> (length rest) 0) (in rest 0) 0) 0xff))
|
|
(let [b (buffer/new 0)]
|
|
(each x (realize-for-iteration a) (buffer/push-byte b (band x 0xff)))
|
|
b)))
|
|
|
|
(defn core-aset-byte [arr i v] (put arr i (band v 0xff)) v)
|
|
(defn core-aset-int [arr i v] (put arr i v) v)
|
|
(defn core-aset-long [arr i v] (put arr i v) v)
|
|
(defn core-aset-short [arr i v] (put arr i v) v)
|
|
(defn core-aset-double [arr i v] (put arr i v) v)
|
|
(defn core-aset-float [arr i v] (put arr i v) v)
|
|
(defn core-aset-char [arr i v] (put arr i v) v)
|
|
(defn core-aset-boolean [arr i v] (put arr i v) v)
|
|
|
|
(defn core-make-array [a & rest]
|
|
# (make-array len) or (make-array type len ...); ignore the type tag
|
|
(let [len (if (number? a) a (in rest 0))] (array/new-filled len nil)))
|
|
|
|
(defn core-into-array [a & rest]
|
|
(let [s (if (> (length rest) 0) (in rest 0) a)]
|
|
(array ;(realize-for-iteration s))))
|
|
|
|
(defn core-to-array [coll]
|
|
(def arr @[]) (each x (realize-for-iteration coll) (array/push arr x)) arr)
|
|
(defn core-to-array-2d [coll]
|
|
(def arr @[]) (each row (realize-for-iteration coll) (array/push arr (core-to-array row))) arr)
|
|
|
|
# Array-element casts — identity on arrays; `bytes` coerces to a byte buffer.
|
|
(defn core-bytes [x] (if (buffer? x) x (core-byte-array x)))
|
|
(defn core-booleans [x] x)
|
|
(defn core-ints [x] x)
|
|
(defn core-longs [x] x)
|
|
(defn core-shorts [x] x)
|
|
(defn core-doubles [x] x)
|
|
(defn core-floats [x] x)
|
|
(defn core-chars [x] x)
|
|
|
|
# Scalar numeric coercions
|
|
(defn core-byte [x] (let [b (band (math/trunc x) 0xff)] (if (>= b 128) (- b 256) b)))
|
|
(defn core-short [x] (let [s (band (math/trunc x) 0xffff)] (if (>= s 0x8000) (- s 0x10000) s)))
|
|
(defn core-unchecked-byte [x] (band (math/trunc x) 0xff))
|
|
(defn core-unchecked-short [x] (band (math/trunc x) 0xffff))
|
|
(defn core-unchecked-char [x] (band (math/trunc x) 0xffff))
|
|
(defn core-unchecked-float [x] (* 1.0 x))
|
|
(defn core-unchecked-double [x] (* 1.0 x))
|
|
|
|
# 64-bit integers (Janet int/s64 — C-backed)
|
|
(defn core-bigint [x] (int/s64 x))
|
|
(defn core-biginteger [x] (int/s64 x))
|
|
(defn core-bigdec [x] (* 1.0 x)) # no BigDecimal; use a double
|
|
|
|
# Chunked seqs — Jolt does not chunk, so these are simple eager equivalents.
|
|
(defn core-chunk-buffer [capacity] @[])
|
|
(defn core-chunk-append [b x] (array/push b x) b)
|
|
(defn core-chunk [b] b)
|
|
(defn core-chunked-seq? [x] false)
|
|
(defn core-chunk-first [s] (core-first s))
|
|
(defn core-chunk-rest [s] (core-rest s))
|
|
(defn core-chunk-next [s] (core-next s))
|
|
(defn core-chunk-cons [chunk rest] (core-concat (realize-for-iteration chunk) rest))
|
|
|
|
# More clojure.core: real implementations backed by existing Jolt machinery.
|
|
(defn core-boolean [x] (if x true false))
|
|
(defn core-cat [rf]
|
|
(fn [& a]
|
|
(case (length a)
|
|
0 (rf) 1 (rf (a 0))
|
|
(do (var acc (a 0)) (each x (realize-for-iteration (a 1)) (set acc (rf acc x))) acc))))
|
|
(defn core-rationalize [x] x)
|
|
(defn core-random-sample [prob & rest]
|
|
(if (= 0 (length rest))
|
|
(core-filter (fn [_] (< (math/random) prob)))
|
|
(core-filter (fn [_] (< (math/random) prob)) (in rest 0))))
|
|
(defn core-reader-conditional [form splicing?]
|
|
@{:jolt/type :jolt/reader-conditional :form form :splicing? splicing?})
|
|
(defn core-reader-conditional? [x]
|
|
(and (table? x) (= :jolt/reader-conditional (get x :jolt/type))))
|
|
(defn core-sorted-map-by [cmp & kvs] (apply core-sorted-map kvs))
|
|
(defn core-sorted-set-by [cmp & xs] (apply core-sorted-set xs))
|
|
(defn core-array-seq [arr & _] (core-seq arr))
|
|
(defn core-seque [& args] (in args (- (length args) 1)))
|
|
(defn core-supers [x] (make-phs))
|
|
(defn core-class [x]
|
|
(cond
|
|
(nil? x) nil (number? x) "java.lang.Number" (string? x) "java.lang.String"
|
|
(boolean? x) "java.lang.Boolean" (keyword? x) "clojure.lang.Keyword"
|
|
(function? x) "clojure.lang.IFn" (buffer? x) "[B"
|
|
(string (type x))))
|
|
(defn core-clojure-version [] "1.11.0-jolt")
|
|
(defn core-munge [s]
|
|
(string/replace-all "-" "_" (string s)))
|
|
(defn core-namespace-munge [s]
|
|
(string/replace-all "-" "_" (string s)))
|
|
(defn core-test [v]
|
|
(let [t (and (core-meta v) (get (core-meta v) :test))]
|
|
(if t (do (t) :ok) :no-test)))
|
|
|
|
|
|
# ============================================================
|
|
# Bit operations (needed for persistent data structures)
|
|
# ============================================================
|
|
|
|
(def core-bit-and (fn [a b] (band a b)))
|
|
(def core-bit-or (fn [a b] (bor a b)))
|
|
(def core-bit-xor (fn [a b] (bxor a b)))
|
|
(def core-bit-not (fn [a] (bnot a)))
|
|
(def core-bit-shift-left (fn [x n] (blshift x n)))
|
|
(def core-bit-shift-right (fn [x n] (brshift x n)))
|
|
(def core-bit-clear (fn [x n] (band x (bnot (blshift 1 n)))))
|
|
(def core-bit-set (fn [x n] (bor x (blshift 1 n))))
|
|
(def core-bit-flip (fn [x n] (bxor x (blshift 1 n))))
|
|
(def core-bit-test (fn [x n] (not= 0 (band x (blshift 1 n)))))
|
|
(def core-bit-and-not (fn [a b] (band a (bnot b))))
|
|
(def core-unsigned-bit-shift-right (fn [x n] (brushift x n)))
|
|
|
|
# ============================================================
|
|
# Integer coercion
|
|
# ============================================================
|
|
|
|
(def core-int (fn [x] (if (core-char? x) (x :ch) (math/trunc x))))
|
|
(def core-long (fn [x] (if (core-char? x) (x :ch) (math/trunc x))))
|
|
(def core-double (fn [x] (* 1.0 (if (core-char? x) (x :ch) x))))
|
|
(def core-float core-double)
|
|
(def core-num (fn [x] (if (number? x) x (error (string "num requires a number, got " (type x))))))
|
|
(defn core-char [x]
|
|
"(char code-or-char) -> a character value."
|
|
(cond
|
|
(core-char? x) x
|
|
(number? x) (make-char (math/trunc x))
|
|
(string? x) (make-char (in x 0))
|
|
(error "char expects a number or character")))
|
|
(def core-unchecked-inc (fn [x] (+ x 1)))
|
|
(def core-unchecked-dec (fn [x] (- x 1)))
|
|
(def core-unchecked-add (fn [& xs] (+ ;xs)))
|
|
(def core-unchecked-subtract (fn [& xs] (- ;xs)))
|
|
|
|
# ============================================================
|
|
# Hash
|
|
# ============================================================
|
|
|
|
(def core-hash (fn [x] (hash x)))
|
|
|
|
|
|
# ============================================================
|
|
# Atom
|
|
# ============================================================
|
|
|
|
(defn core-atom
|
|
"Create an atom. Accepts optional :validator fn and :meta map."
|
|
[val & opts]
|
|
(var atm @{:jolt/type :jolt/atom :value val :watches @{} :validator nil})
|
|
(var i 0)
|
|
(while (< i (length opts))
|
|
(case (opts i)
|
|
:validator (put atm :validator (opts (+ i 1)))
|
|
:meta (let [m (opts (+ i 1))]
|
|
(var meta-tab @{})
|
|
(each k (keys m) (put meta-tab k (get m k)))
|
|
(table/setproto atm meta-tab)
|
|
(put atm :jolt/meta m)))
|
|
(+= i 2))
|
|
atm)
|
|
|
|
(defn core-atom? [x]
|
|
(and (table? x) (= :jolt/atom (x :jolt/type))))
|
|
|
|
# Futures — run the body on a real OS thread (ev/thread) for true parallelism.
|
|
# Janet threads have separate heaps, so the thunk and the state it closes over are
|
|
# MARSHALLED (copied) to the worker thread and the result is marshalled back. A
|
|
# future therefore sees a *snapshot* of captured state and communicates only via
|
|
# its return value — mutating a captured atom does not propagate to the parent.
|
|
# Coordination uses two channels: a thread-chan carries the single [:ok v] /
|
|
# [:error e] result back, and a parent-local chan acts as a broadcast latch that
|
|
# is closed when the result lands so any number of deref-ers can unpark.
|
|
(defn core-future? [x] (and (table? x) (= :jolt/future (x :jolt/type))))
|
|
|
|
(defn core-future-call [thunk]
|
|
(def tc (ev/thread-chan 1)) # worker thread -> collector (shared, thread-safe)
|
|
(def latch (ev/chan)) # parent-local: closed when the result is in
|
|
(def fut @{:jolt/type :jolt/future :latch latch :cached false :res nil :cancelled false})
|
|
# Worker: compute on a fresh OS thread, send back a marshalled result. The give
|
|
# is guarded so a non-marshallable value can't strand deref-ers forever.
|
|
(ev/spawn-thread
|
|
(def res (try [:ok (thunk)] ([e] [:error e])))
|
|
(try (ev/give tc res)
|
|
([_] (ev/give tc [:error "future result is not marshallable across threads"]))))
|
|
# Collector: a parent-side fiber bridges the single result into the box and
|
|
# closes the latch to wake every waiter. If the future was already cancelled,
|
|
# the box is finalized — drop the late result and don't re-close the latch.
|
|
(ev/spawn
|
|
(def res (ev/take tc))
|
|
(when (not (fut :cancelled))
|
|
(put fut :res res)
|
|
(put fut :cached true)
|
|
(try (ev/chan-close latch) ([_] nil))))
|
|
fut)
|
|
|
|
(defn- future-result [fut]
|
|
(def res (fut :res))
|
|
(if (= :error (in res 0)) (error (in res 1)) (in res 1)))
|
|
|
|
(defn core-future-done? [x]
|
|
(if (core-future? x) (truthy? (x :cached))
|
|
(error "future-done? requires a future")))
|
|
(defn core-future-cancelled? [x] (and (core-future? x) (truthy? (x :cancelled))))
|
|
# Janet OS threads can't be interrupted, so the worker still runs to completion
|
|
# in the background; we can only mark the *future* cancelled (done) so deref
|
|
# raises and realized?/future-done?/future-cancelled? reflect it. Returns false
|
|
# if the future has already completed (matching Clojure).
|
|
(defn core-future-cancel [x]
|
|
(if (and (core-future? x) (not (x :cached)) (not (x :cancelled)))
|
|
(do
|
|
(put x :cancelled true)
|
|
(put x :res [:error "future cancelled"])
|
|
(put x :cached true)
|
|
(try (ev/chan-close (x :latch)) ([_] nil))
|
|
true)
|
|
false))
|
|
|
|
# future macro: (future body...) -> (future-call (fn* [] body...))
|
|
(defn core-future [& body]
|
|
@[{:jolt/type :symbol :ns nil :name "future-call"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn*"} [] ;body]])
|
|
|
|
(defn core-deref [ref & opts]
|
|
(cond
|
|
(and (table? ref) (= :jolt/atom (ref :jolt/type)))
|
|
(ref :value)
|
|
(and (table? ref) (= :jolt/volatile (ref :jolt/type)))
|
|
(ref :val)
|
|
(and (table? ref) (= :jolt/delay (ref :jolt/type)))
|
|
(if (ref :realized) (ref :val)
|
|
(let [v ((ref :fn))] (put ref :val v) (put ref :realized true) v))
|
|
(and (table? ref) (= :jolt/future (ref :jolt/type)))
|
|
(if (empty? opts)
|
|
(do (when (not (ref :cached)) (ev/take (ref :latch))) (future-result ref))
|
|
# (deref future timeout-ms timeout-val): wait at most timeout-ms. The
|
|
# deadline cancels the parked take; if the result still hasn't landed we
|
|
# return the supplied timeout value (the future keeps running).
|
|
(let [timeout-val (in opts 1)]
|
|
(when (not (ref :cached))
|
|
(try (ev/with-deadline (/ (in opts 0) 1000) (ev/take (ref :latch))) ([_] nil)))
|
|
(if (ref :cached) (future-result ref) timeout-val)))
|
|
(and (table? ref) (= :jolt/var (ref :jolt/type)))
|
|
(ref :root)
|
|
ref))
|
|
|
|
(defn- atom-validate
|
|
"Call validator on atm. Returns the value if valid, errors otherwise."
|
|
[atm val]
|
|
(let [v (atm :validator)]
|
|
(if v
|
|
(if (v val) val
|
|
(error "Validator rejected value"))
|
|
val)))
|
|
|
|
(defn- atom-notify-watches
|
|
[atm old-val new-val]
|
|
(loop [[k w] :pairs (atm :watches)]
|
|
(w k atm old-val new-val)))
|
|
|
|
(defn core-reset! [atm val]
|
|
(let [old-val (atm :value)]
|
|
(atom-validate atm val)
|
|
(put atm :value val)
|
|
(atom-notify-watches atm old-val val)
|
|
val))
|
|
|
|
(defn core-swap! [atm f & args]
|
|
(var old-val (atm :value))
|
|
(var new-val (apply f old-val args))
|
|
(atom-validate atm new-val)
|
|
(put atm :value new-val)
|
|
(atom-notify-watches atm old-val new-val)
|
|
new-val)
|
|
|
|
(defn core-reset-vals! [atm val]
|
|
(let [old-val (atm :value)]
|
|
(atom-validate atm val)
|
|
(put atm :value val)
|
|
(atom-notify-watches atm old-val val)
|
|
[old-val val]))
|
|
|
|
(defn core-swap-vals! [atm f & args]
|
|
(var old-val (atm :value))
|
|
(var new-val (apply f old-val args))
|
|
(atom-validate atm new-val)
|
|
(put atm :value new-val)
|
|
(atom-notify-watches atm old-val new-val)
|
|
[old-val new-val])
|
|
|
|
(defn core-compare-and-set! [atm old-val new-val]
|
|
(if (= old-val (atm :value))
|
|
(do
|
|
(atom-validate atm new-val)
|
|
(put atm :value new-val)
|
|
(atom-notify-watches atm old-val new-val)
|
|
true)
|
|
false))
|
|
|
|
(defn core-set-validator! [atm validator-fn]
|
|
(put atm :validator validator-fn)
|
|
nil)
|
|
|
|
(defn core-get-validator [atm]
|
|
(atm :validator))
|
|
|
|
(defn core-add-watch [atm key watch-fn]
|
|
(let [watches (atm :watches)]
|
|
(put watches key watch-fn)
|
|
atm))
|
|
|
|
(defn core-remove-watch [atm key]
|
|
(let [watches (atm :watches)]
|
|
(put watches key nil)
|
|
atm))
|
|
|
|
# ============================================================
|
|
# Threading macros (as regular functions? No, as macros in Clojure)
|
|
# These need to be defined as macros in the Jolt namespace system.
|
|
# For now, skip — they need proper macro definition via the evaluator.
|
|
# ============================================================
|
|
|
|
# ============================================================
|
|
# Initialization — intern everything into a context's namespace
|
|
# ============================================================
|
|
|
|
(def gensym_counter @{:val 0})
|
|
|
|
(defn gensym
|
|
"Returns a new symbol with a unique name."
|
|
[&opt prefix-string]
|
|
(default prefix-string "G__")
|
|
(def n (get gensym_counter :val))
|
|
(put gensym_counter :val (+ n 1))
|
|
{:jolt/type :symbol :ns nil :name (string prefix-string n)})
|
|
|
|
(defn core-cond
|
|
"Macro: (cond test1 expr1 test2 expr2 ... :else default)
|
|
-> (if test1 expr1 (if test2 expr2 ...))"
|
|
[& clauses]
|
|
(defn build [cls]
|
|
(if (= 0 (length cls))
|
|
nil
|
|
(let [t (first cls)]
|
|
(if (= :else t)
|
|
(if (> (length cls) 1) (in cls 1) nil)
|
|
(if (< (length cls) 2)
|
|
(error "cond requires an even number of forms")
|
|
(let [e (in cls 1)]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
t e
|
|
(build (tuple/slice cls 2))]))))))
|
|
(build clauses))
|
|
|
|
(defn core-case
|
|
"Macro: (case expr val1 result1 ... default)
|
|
Supports single values, lists of values (one-of-many), and symbols."
|
|
[expr & clauses]
|
|
(def g (gensym))
|
|
(defn make-const [c]
|
|
# case constants are literals, never evaluated: quote symbols and list
|
|
# literals (read as arrays) so e.g. `sym` and a wrapped list `(a b c)` match
|
|
# by value rather than resolving/calling.
|
|
(if (or (and (struct? c) (= :symbol (c :jolt/type))) (array? c))
|
|
@[{:jolt/type :symbol :ns nil :name "quote"} c]
|
|
c))
|
|
(defn make-test [c]
|
|
(if (array? c)
|
|
(let [or-args @[{:jolt/type :symbol :ns nil :name "or"}]]
|
|
(each v c
|
|
(array/push or-args @[{:jolt/type :symbol :ns nil :name "="} g (make-const v)]))
|
|
or-args)
|
|
@[{:jolt/type :symbol :ns nil :name "="} g (make-const c)]))
|
|
(defn build [cls]
|
|
(if (= 0 (length cls))
|
|
nil
|
|
(if (= 1 (length cls))
|
|
(first cls)
|
|
(let [c (first cls)
|
|
r (first (tuple/slice cls 1))]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
(make-test c)
|
|
r
|
|
(build (tuple/slice cls 2))]))))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses)])
|
|
|
|
(defn core-when
|
|
"Macro: (when test & body) -> (if test (do body...))"
|
|
[test & body]
|
|
(def arr (array ;body))
|
|
(array/insert arr 0 {:jolt/type :symbol :ns nil :name "do"})
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
test
|
|
arr])
|
|
|
|
(defn core-when-not
|
|
"Macro: (when-not test & body) -> (when (not test) & body)"
|
|
[test & body]
|
|
(def not-form @[{:jolt/type :symbol :ns nil :name "not"} test])
|
|
@[{:jolt/type :symbol :ns nil :name "if"} not-form
|
|
@[{:jolt/type :symbol :ns nil :name "do"} ;body]])
|
|
|
|
(defn core-and
|
|
"Macro: (and) -> true, (and x) -> x, (and x y ...) -> (if x (and y ...) x)"
|
|
[& exprs]
|
|
(if (= 0 (length exprs)) true
|
|
(if (= 1 (length exprs)) (first exprs)
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "and__x"} (first exprs)]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
{:jolt/type :symbol :ns nil :name "and__x"}
|
|
@[{:jolt/type :symbol :ns nil :name "and"} ;(tuple/slice exprs 1)]
|
|
{:jolt/type :symbol :ns nil :name "and__x"}]])))
|
|
|
|
(defn core-or
|
|
"Macro: (or) -> nil, (or x) -> x, (or x y ...) -> (let [or__x x] (if or__x or__x (or y ...)))"
|
|
[& exprs]
|
|
(if (= 0 (length exprs)) nil
|
|
(if (= 1 (length exprs)) (first exprs)
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "or__x"} (first exprs)]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
{:jolt/type :symbol :ns nil :name "or__x"}
|
|
{:jolt/type :symbol :ns nil :name "or__x"}
|
|
@[{:jolt/type :symbol :ns nil :name "or"} ;(tuple/slice exprs 1)]]])))
|
|
|
|
(defn core-if-let
|
|
"Macro: (if-let [binding val-expr] then else?)"
|
|
[bindings then-form & else-forms]
|
|
(def form-sym (in bindings 0))
|
|
(def val-form (in bindings 1))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[form-sym val-form]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
form-sym
|
|
then-form
|
|
;else-forms]])
|
|
|
|
(defn core-when-let
|
|
"Macro: (when-let [binding val-expr] & body)"
|
|
[bindings & body]
|
|
(def form-sym (in bindings 0))
|
|
(def val-form (in bindings 1))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[form-sym val-form]
|
|
@[{:jolt/type :symbol :ns nil :name "when"}
|
|
form-sym
|
|
;body]])
|
|
|
|
(defn core-if-some
|
|
"Macro: (if-some [binding val-expr] then else?)"
|
|
[bindings then-form & else-forms]
|
|
(def form-sym (in bindings 0))
|
|
(def val-form (in bindings 1))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[form-sym val-form]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
|
|
then-form
|
|
;else-forms]])
|
|
|
|
(defn core-when-some
|
|
"Macro: (when-some [binding val-expr] & body)"
|
|
[bindings & body]
|
|
(def form-sym (in bindings 0))
|
|
(def val-form (in bindings 1))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[form-sym val-form]
|
|
@[{:jolt/type :symbol :ns nil :name "when"}
|
|
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
|
|
;body]])
|
|
|
|
(defn core-doto
|
|
"Macro: (doto obj (method args)...) → let obj, call methods, return obj"
|
|
[obj & forms]
|
|
(def sym (gensym "doto"))
|
|
(def result @[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[sym obj]])
|
|
(each f forms
|
|
(if (array? f)
|
|
# (doto x (f a b)) -> (f x a b) (thread x as first arg, not a method call)
|
|
(array/push result @[(first f) sym ;(tuple/slice f 1)])
|
|
(array/push result @[f sym])))
|
|
(array/push result sym)
|
|
result)
|
|
|
|
(defn core-if-not
|
|
"Macro: (if-not test then else?) -> (if (not test) then else?)"
|
|
[test then-form & else-forms]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
@[{:jolt/type :symbol :ns nil :name "not"} test]
|
|
then-form
|
|
;else-forms])
|
|
|
|
(defn core-when-first
|
|
"Macro: (when-first [sym coll] & body) -> (when-let [sym (first coll)] body...)"
|
|
[bindings & body]
|
|
(def sym (in bindings 0))
|
|
(def coll-form (in bindings 1))
|
|
@[{:jolt/type :symbol :ns nil :name "when-let"}
|
|
@[sym @[{:jolt/type :symbol :ns nil :name "first"} coll-form]]
|
|
;body])
|
|
|
|
(defn core-condp
|
|
"Macro: (condp pred expr clause1 val1 ... default)"
|
|
[pred expr & clauses]
|
|
(def g (gensym))
|
|
(defn build [cls]
|
|
(if (= 0 (length cls))
|
|
nil
|
|
(if (= 1 (length cls))
|
|
(first cls)
|
|
(let [c (first cls)
|
|
v (first (tuple/slice cls 1))]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
(if (and (struct? c) (= :symbol (c :jolt/type)) (= ":>>" (c :name)))
|
|
@[v g]
|
|
@[pred c g])
|
|
v
|
|
(build (tuple/slice cls 2))]))))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses)])
|
|
|
|
(defn core-dotimes
|
|
"Macro: (dotimes [sym n] & body) -> loop from 0 to n-1"
|
|
[bindings & body]
|
|
(def sym (in bindings 0))
|
|
(def n-form (in bindings 1))
|
|
(def i (gensym))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[i n-form]
|
|
@[{:jolt/type :symbol :ns nil :name "loop*"}
|
|
@[sym 0]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
@[{:jolt/type :symbol :ns nil :name "<"} sym i]
|
|
@[{:jolt/type :symbol :ns nil :name "do"}
|
|
;body
|
|
@[{:jolt/type :symbol :ns nil :name "recur"}
|
|
@[{:jolt/type :symbol :ns nil :name "inc"} sym]]]
|
|
nil]]])
|
|
|
|
(defn core-while
|
|
"Macro: (while test & body) -> loop while test is truthy"
|
|
[test & body]
|
|
@[{:jolt/type :symbol :ns nil :name "loop*"}
|
|
@[]
|
|
@[{:jolt/type :symbol :ns nil :name "when"}
|
|
test
|
|
@[{:jolt/type :symbol :ns nil :name "do"} ;body]
|
|
@[{:jolt/type :symbol :ns nil :name "recur"}]]])
|
|
|
|
(defn core-for
|
|
"Macro: (for [binding-form coll :when test :let [bindings]] body)
|
|
List comprehension. Basic support for :when and :let."
|
|
[bindings body]
|
|
(defn parse-groups [bvec]
|
|
(var groups @[])
|
|
(var i 0)
|
|
(while (< i (length bvec))
|
|
(def bind (bvec i))
|
|
(var coll (bvec (+ i 1)))
|
|
(def mods @[])
|
|
(+= i 2)
|
|
(while (and (< i (length bvec)) (keyword? (bvec i)))
|
|
(case (bvec i)
|
|
:when (do (array/push mods @[{:jolt/type :symbol :ns nil :name "when"} (bvec (+ i 1))]) (+= i 2))
|
|
:let (do (array/push mods @[{:jolt/type :symbol :ns nil :name "let"} (bvec (+ i 1))]) (+= i 2))
|
|
# :while terminates iteration of this binding's collection
|
|
:while (do (set coll @[{:jolt/type :symbol :ns nil :name "take-while"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn"} [bind] (bvec (+ i 1))]
|
|
coll])
|
|
(+= i 2))
|
|
(do (+= i 1))))
|
|
(array/push groups @[bind coll mods]))
|
|
groups)
|
|
(defn wrap-mods [mods inner-form]
|
|
(if (= 0 (length mods))
|
|
inner-form
|
|
(let [m (in mods (- (length mods) 1))
|
|
rest-mods (array/slice mods 0 (- (length mods) 1))
|
|
kind (get (m 0) :name)]
|
|
(wrap-mods rest-mods
|
|
(if (= kind "when")
|
|
@[{:jolt/type :symbol :ns nil :name "if"} (m 1)
|
|
@[{:jolt/type :symbol :ns nil :name "list"} inner-form] @[]]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} (m 1) inner-form])))))
|
|
(defn build [group-idx groups]
|
|
(if (>= group-idx (length groups))
|
|
body
|
|
(let [g (in groups group-idx)
|
|
my-bind (in g 0)
|
|
my-coll (in g 1)
|
|
my-mods (in g 2)
|
|
inner (build (+ group-idx 1) groups)
|
|
inner-form (wrap-mods my-mods inner)
|
|
is-last (= group-idx (- (length groups) 1))
|
|
has-mods (> (length my-mods) 0)]
|
|
(if (and is-last (not has-mods))
|
|
@[{:jolt/type :symbol :ns nil :name "map"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn"} [my-bind] inner-form]
|
|
my-coll]
|
|
@[{:jolt/type :symbol :ns nil :name "mapcat"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn"} [my-bind] inner-form]
|
|
my-coll]))))
|
|
(if (>= (length bindings) 2)
|
|
(build 0 (parse-groups bindings))
|
|
body))
|
|
|
|
(defn core-thread-first
|
|
"Macro: (-> x & forms) — thread first"
|
|
[x & forms]
|
|
(if (= 0 (length forms)) x
|
|
(let [f (first forms)
|
|
rest-forms (tuple/slice forms 1)]
|
|
(if (array? f)
|
|
(apply core-thread-first [(let [arr (array/slice f)]
|
|
(array/insert arr 1 x)
|
|
arr) ;rest-forms])
|
|
(apply core-thread-first [@[f x] ;rest-forms])))))
|
|
|
|
(defn core-thread-last
|
|
"Macro: (->> x & forms) — thread last"
|
|
[x & forms]
|
|
(if (= 0 (length forms)) x
|
|
(let [f (first forms)
|
|
rest-forms (tuple/slice forms 1)]
|
|
(if (array? f)
|
|
(apply core-thread-last [(let [arr (array/slice f)]
|
|
(array/push arr x)
|
|
arr) ;rest-forms])
|
|
(apply core-thread-last [@[f x] ;rest-forms])))))
|
|
|
|
(defn core-some->
|
|
"Macro: (some-> expr & forms) — thread first, stop at nil"
|
|
[expr & forms]
|
|
(if (= 0 (length forms)) expr
|
|
(let [f (first forms)
|
|
rest-forms (tuple/slice forms 1)]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "some->__x"} expr]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
@[{:jolt/type :symbol :ns nil :name "some?"}
|
|
{:jolt/type :symbol :ns nil :name "some->__x"}]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "some->__x"}
|
|
(if (array? f)
|
|
(let [arr (array/slice f)]
|
|
(array/insert arr 1 {:jolt/type :symbol :ns nil :name "some->__x"})
|
|
arr)
|
|
@[f {:jolt/type :symbol :ns nil :name "some->__x"}])]
|
|
(apply core-some-> [{:jolt/type :symbol :ns nil :name "some->__x"} ;rest-forms])]
|
|
nil]])))
|
|
|
|
(defn core-some->>
|
|
"Macro: (some->> expr & forms) — thread last, stop at nil"
|
|
[expr & forms]
|
|
(if (= 0 (length forms)) expr
|
|
(let [f (first forms)
|
|
rest-forms (tuple/slice forms 1)]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "some->__x"} expr]
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
@[{:jolt/type :symbol :ns nil :name "some?"}
|
|
{:jolt/type :symbol :ns nil :name "some->__x"}]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[{:jolt/type :symbol :ns nil :name "some->__x"}
|
|
(if (array? f)
|
|
(let [arr (array/slice f)]
|
|
(array/push arr {:jolt/type :symbol :ns nil :name "some->__x"})
|
|
arr)
|
|
@[f {:jolt/type :symbol :ns nil :name "some->__x"}])]
|
|
(apply core-some->> [{:jolt/type :symbol :ns nil :name "some->__x"} ;rest-forms])]
|
|
nil]])))
|
|
|
|
(defn core-cond->
|
|
"Macro: (cond-> expr test form ...) — thread first only when test is true"
|
|
[expr & clauses]
|
|
(def g (gensym))
|
|
(defn build [cls result-form]
|
|
(if (= 0 (length cls))
|
|
result-form
|
|
(let [t (first cls)
|
|
f (in cls 1)
|
|
f-call (if (array? f)
|
|
(let [arr (array/slice f)]
|
|
(array/insert arr 1 result-form)
|
|
arr)
|
|
@[f result-form])]
|
|
(build (tuple/slice cls 2)
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
t
|
|
f-call
|
|
result-form]))))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses g)])
|
|
|
|
(defn core-cond->>
|
|
"Macro: (cond->> expr test form ...) — thread last only when test is true"
|
|
[expr & clauses]
|
|
(def g (gensym))
|
|
(defn build [cls result-form]
|
|
(if (= 0 (length cls))
|
|
result-form
|
|
(let [t (first cls)
|
|
f (in cls 1)
|
|
f-call (if (array? f)
|
|
(let [arr (array/slice f)]
|
|
(array/push arr result-form)
|
|
arr)
|
|
@[f result-form])]
|
|
(build (tuple/slice cls 2)
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
t
|
|
f-call
|
|
result-form]))))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses g)])
|
|
|
|
(defn core-as->
|
|
"Macro: (as-> expr name & forms) — bind name to expr, thread through forms"
|
|
[expr name & forms]
|
|
(defn build [fs acc]
|
|
(if (= 0 (length fs))
|
|
acc
|
|
(let [f (first fs)]
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
@[name acc]
|
|
(build (tuple/slice fs 1) f)])))
|
|
(build forms expr))
|
|
|
|
(defn core-push-thread-bindings [b] (push-thread-bindings b))
|
|
(defn core-pop-thread-bindings [] (pop-thread-bindings))
|
|
|
|
(defn core-var-get [v] (var-get v))
|
|
(defn core-var-set [v val] (var-set v val))
|
|
(defn core-var? [x] (var? x))
|
|
(defn core-alter-var-root [v f & args] (apply alter-var-root v f args))
|
|
(defn core-alter-meta! [v f & args] (apply alter-meta! v f args))
|
|
(defn core-reset-meta! [v meta] (reset-meta! v meta))
|
|
|
|
(defn core-intern [ns-name sym-name val] val)
|
|
|
|
(defn core-binding
|
|
"Macro: (binding [var val ...] body...)
|
|
Uses array-map (plain struct) to store binding frame
|
|
to avoid PHM get() incompatibility with var-get."
|
|
[bindings & body]
|
|
(def frame-pairs @[])
|
|
(var i 0)
|
|
(let [n (length bindings)]
|
|
(while (< i n)
|
|
(array/push frame-pairs
|
|
@[{:jolt/type :symbol :ns nil :name "var"} (in bindings i)])
|
|
(array/push frame-pairs (in bindings (+ i 1)))
|
|
(+= i 2)))
|
|
(def hm-form (array/insert frame-pairs 0
|
|
{:jolt/type :symbol :ns nil :name "array-map"}))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"}
|
|
[{:jolt/type :symbol :ns nil :name "frame"} hm-form]
|
|
@[{:jolt/type :symbol :ns nil :name "push-thread-bindings"}
|
|
{:jolt/type :symbol :ns nil :name "frame"}]
|
|
@[{:jolt/type :symbol :ns nil :name "try"}
|
|
@[{:jolt/type :symbol :ns nil :name "do"} ;body]
|
|
@[{:jolt/type :symbol :ns nil :name "finally"}
|
|
@[{:jolt/type :symbol :ns nil :name "pop-thread-bindings"}]]]])
|
|
|
|
|
|
(defn- defn->def
|
|
"Shared expansion for defn/defn-: (name doc-string? attr-map? params body...)
|
|
or (name doc-string? attr-map? ([params] body)... attr-map?) -> (def name (fn* ...))."
|
|
[fn-name rest]
|
|
(var items (array/slice rest))
|
|
# strip optional docstring
|
|
(when (and (> (length items) 0) (string? (first items)))
|
|
(set items (array/slice items 1)))
|
|
# strip optional attr-map (a map literal, i.e. struct/table that isn't a symbol)
|
|
(when (and (> (length items) 0)
|
|
(let [x (first items)]
|
|
(and (or (struct? x) (table? x))
|
|
(not (and (struct? x) (= :symbol (get x :jolt/type)))))))
|
|
(set items (array/slice items 1)))
|
|
(def fn-form @[{:jolt/type :symbol :ns nil :name "fn*"}])
|
|
(if (and (> (length items) 0) (array? (first items)) (indexed? (first (first items))))
|
|
# multi-arity: each remaining item is an ([params] body...) clause
|
|
(each pair items (array/push fn-form pair))
|
|
# single-arity: items = [params-vector body...]
|
|
(do
|
|
(array/push fn-form (first items))
|
|
(each b (tuple/slice items 1) (array/push fn-form b))))
|
|
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])
|
|
|
|
(defn core-defn
|
|
"Macro: (defn name doc-string? attr-map? [args] body...) (or multi-arity)
|
|
-> (def name (fn* ...))"
|
|
[fn-name & rest]
|
|
(defn->def fn-name rest))
|
|
|
|
# defn- — same as defn (private not enforced in Jolt)
|
|
(defn core-defn- [fn-name & rest]
|
|
(defn->def fn-name rest))
|
|
|
|
# Hierarchy stubs for sci bootstrap
|
|
(def core-make-hierarchy make-hierarchy)
|
|
(defn core-derive
|
|
[& args]
|
|
(case (length args)
|
|
2 (let [[tag parent] args] (derive* the-global-hierarchy tag parent) nil)
|
|
3 (let [[h tag parent] args] (derive* h tag parent))))
|
|
(defn core-isa?
|
|
[& args]
|
|
(case (length args)
|
|
2 (let [[child parent] args] (isa? the-global-hierarchy child parent))
|
|
3 (let [[h child parent] args] (isa? h child parent))))
|
|
(defn core-ancestors
|
|
[& args]
|
|
(case (length args)
|
|
1 (apply make-phs (ancestors the-global-hierarchy (in args 0)))
|
|
2 (let [[h tag] args] (apply make-phs (ancestors h tag)))))
|
|
(defn core-descendants
|
|
[& args]
|
|
(case (length args)
|
|
1 (apply make-phs (descendants the-global-hierarchy (in args 0)))
|
|
2 (let [[h tag] args] (apply make-phs (descendants h tag)))))
|
|
(defn core-parents
|
|
[& args]
|
|
(let [[h tag] (if (= 1 (length args)) [the-global-hierarchy (in args 0)] args)
|
|
p (get (h :parents) tag)]
|
|
(if p (make-phs p) (make-phs))))
|
|
(defn core-underive [& args]
|
|
(case (length args)
|
|
2 (let [[tag parent] args] (underive the-global-hierarchy tag parent) nil)
|
|
3 (let [[h tag parent] args] (underive h tag parent))))
|
|
(def core-get-method (fn [mm-var dispatch-val]
|
|
(let [methods (get mm-var :jolt/methods)]
|
|
(or (get methods dispatch-val) (get methods :default)))))
|
|
(def core-methods (fn [mm-var] (get mm-var :jolt/methods)))
|
|
(def core-remove-method (fn [mm-var dispatch-val]
|
|
(let [methods (get mm-var :jolt/methods)]
|
|
(put methods dispatch-val nil) mm-var)))
|
|
(def core-remove-all-methods (fn [mm-var]
|
|
(put mm-var :jolt/methods @{}) mm-var))
|
|
(defn core-prefer-method [mm-var dispatch-val-a dispatch-val-b]
|
|
(let [prefs (or (get mm-var :jolt/prefers)
|
|
(do (put mm-var :jolt/prefers @{}) (mm-var :jolt/prefers)))]
|
|
(put prefs dispatch-val-a dispatch-val-b) mm-var))
|
|
|
|
(defn core-with-meta [obj meta]
|
|
# Functions and scalars can't carry metadata in Jolt's model — return as-is
|
|
# rather than crashing (Clojure attaches meta only to IObj values).
|
|
(if (or (function? obj) (cfunction? obj) (number? obj) (boolean? obj)
|
|
(nil? obj) (string? obj) (keyword? obj) (buffer? obj))
|
|
obj
|
|
(do
|
|
(var new-obj @{})
|
|
(each k (keys obj)
|
|
(put new-obj k (get obj k)))
|
|
# table/setproto requires a table, convert struct meta to table
|
|
(var meta-tab @{})
|
|
(each k (keys meta) (put meta-tab k (get meta k)))
|
|
(table/setproto new-obj meta-tab)
|
|
(put new-obj :jolt/meta meta)
|
|
new-obj)))
|
|
|
|
(defn core-var-dynamic? [v]
|
|
(var-dynamic? v))
|
|
|
|
# Java interop stubs
|
|
(def core-Object (fn [] (struct ;[:jolt/type :jolt/java-object])))
|
|
|
|
# Volatiles — typed box so deref/volatile? can recognize them.
|
|
(defn core-volatile! [v] @{:jolt/type :jolt/volatile :val v})
|
|
(defn core-volatile? [x] (and (table? x) (= :jolt/volatile (x :jolt/type))))
|
|
(defn core-vswap! [vol f & args]
|
|
(def new-val (apply f (vol :val) args))
|
|
(put vol :val new-val)
|
|
new-val)
|
|
(defn core-vreset! [vol val] (put vol :val val) val)
|
|
|
|
# Delays — created lazily by the `delay` macro; forced once via force/deref.
|
|
(defn core-make-delay [thunk] @{:jolt/type :jolt/delay :fn thunk :realized false :val nil})
|
|
(defn core-delay? [x] (and (table? x) (= :jolt/delay (x :jolt/type))))
|
|
(defn core-force [x]
|
|
(if (core-delay? x)
|
|
(if (x :realized) (x :val)
|
|
(let [v ((x :fn))] (put x :val v) (put x :realized true) v))
|
|
x))
|
|
(defn core-realized? [x]
|
|
(cond
|
|
(core-delay? x) (x :realized)
|
|
(core-future? x) (truthy? (x :cached))
|
|
(lazy-seq? x) (truthy? (x :realized))
|
|
(and (table? x) (= :jolt/atom (x :jolt/type))) true
|
|
# Clojure's realized? is only defined on IPending; reject anything else.
|
|
(error (string "realized? not supported on " (type x)))))
|
|
|
|
# delay macro: (delay body...) -> (make-delay (fn* [] body...))
|
|
(defn core-delay [& body]
|
|
@[{:jolt/type :symbol :ns nil :name "make-delay"}
|
|
@[{:jolt/type :symbol :ns nil :name "fn*"} [] ;body]])
|
|
|
|
# Proxy stub — returns nil form (macro, args not evaluated)
|
|
(defn core-proxy [& args] nil)
|
|
|
|
# Thread stubs
|
|
(def core-Thread (fn [& args] (struct ;[:jolt/type :jolt/thread])))
|
|
(def core-ThreadLocal (fn [& args] (struct ;[:jolt/type :jolt/thread-local])))
|
|
(def core-IllegalStateException (fn [& args] (struct ;[:jolt/type :jolt/exception])))
|
|
|
|
# definterface stub — JVM-only, emits def form
|
|
(defn core-definterface [name-sym & body]
|
|
@[{:jolt/type :symbol :ns nil :name "def"}
|
|
name-sym
|
|
@{}])
|
|
|
|
# comment macro — ignores body, returns nil
|
|
(defn core-comment [& body]
|
|
nil)
|
|
|
|
# defrecord — creates a proper type via deftype + factory functions
|
|
(defn core-defrecord [name-sym fields-vec & body]
|
|
(def type-name (name-sym :name))
|
|
(def type-name-dot (string type-name "."))
|
|
(def arrow-name (string "->" type-name))
|
|
(def map-name (string "map->" type-name))
|
|
|
|
# (deftype TypeName [fields...])
|
|
(def dt-form @[{:jolt/type :symbol :ns nil :name "deftype"} name-sym fields-vec])
|
|
|
|
# Arrow factory: (def ->TypeName (fn [field1 field2 ...] (TypeName. field1 field2 ...)))
|
|
(def arrow-call @[{:jolt/type :symbol :ns nil :name type-name-dot}])
|
|
(each f fields-vec (array/push arrow-call f))
|
|
(def arrow-sym {:jolt/type :symbol :ns nil :name arrow-name})
|
|
(def arrow-body @[{:jolt/type :symbol :ns nil :name "fn"} fields-vec arrow-call])
|
|
|
|
# map-> factory: (def map->TypeName (fn [m] (->TypeName (get m :field1) (get m :field2) ...)))
|
|
(def map-call @[{:jolt/type :symbol :ns nil :name arrow-name}])
|
|
(each f fields-vec
|
|
(array/push map-call @[{:jolt/type :symbol :ns nil :name "get"} {:jolt/type :symbol :ns nil :name (string "m")} (keyword (f :name))]))
|
|
(def map-sym {:jolt/type :symbol :ns nil :name map-name})
|
|
# params must be a tuple (a vector), not an array — fn* treats an array
|
|
# first-arg as multi-arity clauses
|
|
(def map-body @[{:jolt/type :symbol :ns nil :name "fn"} [{:jolt/type :symbol :ns nil :name "m"}] map-call])
|
|
|
|
(def out @[{:jolt/type :symbol :ns nil :name "do"}
|
|
dt-form
|
|
@[{:jolt/type :symbol :ns nil :name "def"} arrow-sym arrow-body]
|
|
@[{:jolt/type :symbol :ns nil :name "def"} map-sym map-body]])
|
|
# Process inline protocol/interface implementations:
|
|
# (defrecord T [fs] Proto (m [this] body) ... Proto2 (m2 [this] body))
|
|
# Emit an extend-type per protocol. Each method body is wrapped in a let that
|
|
# binds the record's fields from the instance (first method param), matching
|
|
# Clojure's field-in-scope semantics for deftype/defrecord methods.
|
|
(var i 0)
|
|
(while (< i (length body))
|
|
(def elem (in body i))
|
|
(if (and (struct? elem) (= :symbol (elem :jolt/type)))
|
|
# protocol name; collect following method specs
|
|
(let [proto-sym elem
|
|
et @[{:jolt/type :symbol :ns nil :name "extend-type"} name-sym proto-sym]]
|
|
(++ i)
|
|
(while (and (< i (length body)) (not (and (struct? (in body i)) (= :symbol ((in body i) :jolt/type)))))
|
|
(let [spec (in body i)
|
|
mname (spec 0)
|
|
argv (spec 1)
|
|
mbody (tuple/slice spec 2)
|
|
instance (in argv 0)
|
|
# (let [f0 (core-get instance :f0) ...] body...)
|
|
field-binds @[]
|
|
_ (each f fields-vec
|
|
(array/push field-binds f)
|
|
(array/push field-binds @[{:jolt/type :symbol :ns nil :name "get"}
|
|
instance (keyword (f :name))]))
|
|
wrapped @[{:jolt/type :symbol :ns nil :name "let"}
|
|
(tuple/slice (tuple ;field-binds)) ;mbody]]
|
|
(array/push et @[mname argv wrapped]))
|
|
(++ i))
|
|
(array/push out et))
|
|
(++ i)))
|
|
out)
|
|
|
|
|
|
# letfn — mutually-recursive local fns. Expands to let* of fn* bindings; jolt
|
|
# closures capture the (shared, mutable) bindings table, so forward references
|
|
# between the fns resolve at call time.
|
|
(defn core-letfn [specs & body]
|
|
(def binds @[])
|
|
(each spec specs
|
|
(let [fname (spec 0)
|
|
rest (tuple/slice spec 1)]
|
|
(array/push binds fname)
|
|
# rest is either ([args] body...) for single-arity or a list of
|
|
# ([args] body) clauses for multi-arity; (fn* ;rest) handles both.
|
|
(array/push binds @[{:jolt/type :symbol :ns nil :name "fn*"} ;rest])))
|
|
@[{:jolt/type :symbol :ns nil :name "let*"} (tuple/slice (tuple ;binds)) ;body])
|
|
|
|
# doseq — like `for` but eager and returns nil. Reuse `for`, force realization
|
|
# with `count`, discard the result.
|
|
(defn core-doseq [bindings & body]
|
|
(def for-body @[{:jolt/type :symbol :ns nil :name "do"} ;body nil])
|
|
@[{:jolt/type :symbol :ns nil :name "do"}
|
|
@[{:jolt/type :symbol :ns nil :name "count"}
|
|
@[{:jolt/type :symbol :ns nil :name "for"} bindings for-body]]
|
|
nil])
|
|
|
|
# assert — (assert x) / (assert x message). Throws when x is falsy.
|
|
(defn core-assert [x & more]
|
|
(def msg-form
|
|
(if (> (length more) 0)
|
|
(first more)
|
|
(let [b @""] (pr-render b x) (string "Assert failed: " (string b)))))
|
|
@[{:jolt/type :symbol :ns nil :name "if"}
|
|
x
|
|
nil
|
|
@[{:jolt/type :symbol :ns nil :name "throw"}
|
|
@[{:jolt/type :symbol :ns nil :name "ex-info"} msg-form {}]]])
|
|
|
|
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)
|
|
(defn core-resolve [sym] nil) # shadowed by the resolve special form (needs ctx)
|
|
(defn core-ns-name [ns]
|
|
# ns object -> its name as a symbol (works whether ns is a table/struct/phm)
|
|
(let [nm (core-get ns :name)]
|
|
(if nm {:jolt/type :symbol :ns nil :name (string nm)} nil)))
|
|
|
|
# update — works on both structs and tables
|
|
(defn core-update [m k f & args]
|
|
(def f (as-fn f))
|
|
(core-assoc m k (apply f (core-get m k) args)))
|
|
|
|
(defn- ks-rest [ks]
|
|
(if (tuple? ks) (tuple/slice ks 1) (array/slice ks 1)))
|
|
|
|
(defn core-assoc-in [m ks v]
|
|
(let [ks (vview ks) k (in ks 0)]
|
|
(if (<= (length ks) 1)
|
|
(core-assoc m k v)
|
|
(let [sub (core-get m k)]
|
|
(core-assoc m k (core-assoc-in (if (nil? sub) {} sub) (ks-rest ks) v))))))
|
|
|
|
(defn core-update-in [m ks f & args]
|
|
(let [ks (vview ks) k (in ks 0)]
|
|
(if (<= (length ks) 1)
|
|
(core-assoc m k (apply f (core-get m k) args))
|
|
(let [sub (core-get m k)]
|
|
(core-assoc m k (apply core-update-in (if (nil? sub) {} sub) (ks-rest ks) f args))))))
|
|
|
|
(defn core-fnil [f & defaults]
|
|
(fn [& args]
|
|
(def new-args (array/slice args))
|
|
(var i 0)
|
|
(each d defaults
|
|
(when (and (< i (length new-args)) (nil? (in new-args i)))
|
|
(put new-args i d))
|
|
(++ i))
|
|
(apply f new-args)))
|
|
|
|
# copy-var stubs for sci.impl.copy-vars (used by sci.impl.namespaces)
|
|
(defn core-copy-core-var [sym] nil)
|
|
(defn core-copy-var [sym & args] nil)
|
|
(defn core-macrofy [sym fn & more] fn)
|
|
(defn core-new-var [sym & args] nil)
|
|
(defn core-avoid-method-too-large [& args] @{})
|
|
|
|
# declare macro — accepts symbols, does nothing (forward declaration)
|
|
(defn core-declare [& syms]
|
|
@[{:jolt/type :symbol :ns nil :name "do"}])
|
|
|
|
(defn core-fn
|
|
"Macro: (fn [args] body) → (fn* [args] body)"
|
|
[& args]
|
|
(def result @[])
|
|
(array/push result {:jolt/type :symbol :ns nil :name "fn*"})
|
|
(each a args (array/push result a))
|
|
result)
|
|
|
|
# --- Destructuring expansion (Clojure's `destructure`) -----------------------
|
|
# Expands a binding vector containing destructuring patterns into a plain binding
|
|
# vector (alternating plain-symbol / init-form), using nth/nthnext/get. Shared by
|
|
# let/loop/fn so BOTH the interpreter and the compiler see only plain bindings —
|
|
# the compiler can then compile destructuring (it never sees a pattern), and the
|
|
# kernel needn't destructure at runtime. Mirrors evaluator/destructure-bind.
|
|
|
|
(defn- d-sym [name] {:jolt/type :symbol :ns nil :name name})
|
|
(defn- d-amp? [x] (and (struct? x) (= :symbol (x :jolt/type)) (= "&" (x :name))))
|
|
(defn- d-plain-sym? [x] (and (struct? x) (= :symbol (x :jolt/type))))
|
|
|
|
(defn- d-find-or [or-map nm]
|
|
# [has-default default-form] for binding name nm in an :or map
|
|
(var found false) (var dv nil)
|
|
(when or-map
|
|
(each k (keys or-map)
|
|
(when (and (d-plain-sym? k) (= nm (k :name)))
|
|
(set found true) (set dv (get or-map k)))))
|
|
[found dv])
|
|
|
|
(var d-process nil)
|
|
|
|
(defn- d-vec [pat g out]
|
|
(var i 0) (var idx 0) (def n (length pat))
|
|
(while (< i n)
|
|
(def elem (in pat i))
|
|
(cond
|
|
(d-amp? elem)
|
|
(do (d-process (in pat (+ i 1)) @[(d-sym "nthnext") g idx] out) (+= i 2))
|
|
(= elem :as)
|
|
(do (d-process (in pat (+ i 1)) g out) (+= i 2))
|
|
true
|
|
(do (d-process elem @[(d-sym "nth") g idx nil] out) (++ idx) (++ i)))))
|
|
|
|
(defn- d-map [pat g out]
|
|
(def or-map (get pat :or))
|
|
(def as-sym (get pat :as))
|
|
(when as-sym (array/push out as-sym) (array/push out g))
|
|
(each spec [[:keys :kw] [:strs :str] [:syms :sym]]
|
|
(def kw (in spec 0)) (def kind (in spec 1)) (def names (get pat kw))
|
|
(when names
|
|
(each s names
|
|
(def is-sym (d-plain-sym? s))
|
|
(def local (if is-sym (s :name) (string s)))
|
|
# A namespaced symbol in :keys/:syms (x/y) looks up the namespaced key
|
|
# but binds the bare local y.
|
|
(def nsp (and is-sym (s :ns)))
|
|
(def keyform (case kind
|
|
:kw (keyword (if nsp (string nsp "/" local) local))
|
|
:str local
|
|
:sym @[(d-sym "quote") {:jolt/type :symbol :ns nsp :name local}]))
|
|
(def fo (d-find-or or-map local))
|
|
(array/push out (d-sym local))
|
|
(array/push out (if (in fo 0)
|
|
@[(d-sym "get") g keyform (in fo 1)]
|
|
@[(d-sym "get") g keyform])))))
|
|
(each k (keys pat)
|
|
(when (not (keyword? k)) # explicit {pattern key-expr}
|
|
(d-process k @[(d-sym "get") g (get pat k)] out))))
|
|
|
|
(set d-process
|
|
(fn dp [pat init out]
|
|
(cond
|
|
(d-plain-sym? pat) (do (array/push out pat) (array/push out init))
|
|
(tuple? pat) (let [g (gensym "_d__")]
|
|
(array/push out g) (array/push out init) (d-vec pat g out))
|
|
(and (struct? pat) (nil? (pat :jolt/type)))
|
|
(let [g (gensym "_d__")]
|
|
(array/push out g) (array/push out init) (d-map pat g out))
|
|
(error "unsupported destructuring pattern"))))
|
|
|
|
(defn core-destructure
|
|
"Clojure `destructure`: binding vector with patterns -> plain binding vector."
|
|
[bindings]
|
|
(def out @[])
|
|
(var i 0) (def n (length bindings))
|
|
(while (< i n) (d-process (in bindings i) (in bindings (+ i 1)) out) (+= i 2))
|
|
(tuple/slice out))
|
|
|
|
(defn core-let
|
|
"Macro: (let [bindings] body) → (let* [plain-bindings] body), expanding
|
|
destructuring patterns so the compiler/interpreter see only plain symbols."
|
|
[bindings & body]
|
|
(def result @[])
|
|
(array/push result {:jolt/type :symbol :ns nil :name "let*"})
|
|
(array/push result (core-destructure bindings))
|
|
(each b body (array/push result b))
|
|
result)
|
|
|
|
(defn core-loop
|
|
"Macro: (loop [bindings] body) → (loop* [bindings] body)"
|
|
[bindings & body]
|
|
(def result @[])
|
|
(array/push result {:jolt/type :symbol :ns nil :name "loop*"})
|
|
(array/push result bindings)
|
|
(each b body (array/push result b))
|
|
result)
|
|
|
|
# Protocol implementation — methods dispatch via type registry
|
|
(defn core-defprotocol [protocol-name & sigs]
|
|
(def result @[])
|
|
(array/push result {:jolt/type :symbol :ns nil :name "do"})
|
|
(def methods @{})
|
|
(each sig sigs
|
|
(def method-name (first sig))
|
|
(def arglists (tuple/slice sig 1))
|
|
(put methods (keyword (if (struct? method-name) (method-name :name) method-name)) {:name method-name :arglists arglists}))
|
|
(def proto-def @[])
|
|
(array/push proto-def {:jolt/type :symbol :ns nil :name "def"})
|
|
(array/push proto-def protocol-name)
|
|
(array/push proto-def @{:jolt/type :jolt/protocol
|
|
:name {:jolt/type :symbol :ns nil :name (protocol-name :name)}
|
|
:methods methods})
|
|
(array/push result proto-def)
|
|
(each sig sigs
|
|
(def method-name (first sig))
|
|
(def method-def @[])
|
|
(array/push method-def {:jolt/type :symbol :ns nil :name "def"})
|
|
(array/push method-def method-name)
|
|
(def fn-form @[])
|
|
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
|
|
(array/push fn-form [{:jolt/type :symbol :ns nil :name "this"} {:jolt/type :symbol :ns nil :name "&"} {:jolt/type :symbol :ns nil :name "rest-args"}])
|
|
(array/push fn-form @[
|
|
{:jolt/type :symbol :ns nil :name "protocol-dispatch"}
|
|
protocol-name
|
|
method-name
|
|
{:jolt/type :symbol :ns nil :name "this"}
|
|
{:jolt/type :symbol :ns nil :name "rest-args"}])
|
|
(array/push method-def fn-form)
|
|
(array/push result method-def))
|
|
result)
|
|
|
|
(defn core-extend-type [type-sym proto-sym & impls]
|
|
(def result @[{:jolt/type :symbol :ns nil :name "do"}])
|
|
(each method-spec impls
|
|
(def method-name (method-spec 0))
|
|
(def arg-vec (method-spec 1))
|
|
(def body (tuple/slice method-spec 2))
|
|
(def fn-form @[{:jolt/type :symbol :ns nil :name "fn*"} arg-vec ;body])
|
|
(array/push result @[
|
|
{:jolt/type :symbol :ns nil :name "register-method"}
|
|
type-sym
|
|
proto-sym
|
|
method-name
|
|
fn-form]))
|
|
result)
|
|
|
|
(defn core-extend-protocol [proto-sym & type-impls]
|
|
(def result @[{:jolt/type :symbol :ns nil :name "do"}])
|
|
(var i 0)
|
|
(while (< i (length type-impls))
|
|
(let [type-sym (type-impls i)
|
|
methods (type-impls (+ i 1))]
|
|
# methods is a single method spec array or an array of method specs
|
|
# If the first element is a symbol (method name), treat as single spec
|
|
(if (and (struct? (methods 0)) (= :symbol ((methods 0) :jolt/type)))
|
|
(let [method-spec methods]
|
|
(def method-name (method-spec 0))
|
|
(def arg-vec (method-spec 1))
|
|
(def body (tuple/slice method-spec 2))
|
|
(def fn-form @[{:jolt/type :symbol :ns nil :name "fn*"} arg-vec ;body])
|
|
(array/push result @[
|
|
{:jolt/type :symbol :ns nil :name "register-method"}
|
|
type-sym
|
|
proto-sym
|
|
method-name
|
|
fn-form]))
|
|
(each method-spec methods
|
|
(def method-name (method-spec 0))
|
|
(def arg-vec (method-spec 1))
|
|
(def body (tuple/slice method-spec 2))
|
|
(def fn-form @[{:jolt/type :symbol :ns nil :name "fn*"} arg-vec ;body])
|
|
(array/push result @[
|
|
{:jolt/type :symbol :ns nil :name "register-method"}
|
|
type-sym
|
|
proto-sym
|
|
method-name
|
|
fn-form]))))
|
|
(+= i 2))
|
|
result)
|
|
|
|
(def core-extend (fn [& args] nil))
|
|
|
|
(defn core-reify [& forms]
|
|
# forms interleaves protocol-name symbols with method specs (name [args] body);
|
|
# collect every method spec (a list), tracking the first protocol for the tag.
|
|
(def result @[{:jolt/type :symbol :ns nil :name "do"}])
|
|
(def methods @{})
|
|
(var proto-sym nil)
|
|
(var i 0)
|
|
(while (< i (length forms))
|
|
(def elem (in forms i))
|
|
(if (and (struct? elem) (= :symbol (elem :jolt/type)))
|
|
(do (when (nil? proto-sym) (set proto-sym elem)) (++ i))
|
|
(let [method-name (in elem 0)
|
|
arg-vec (in elem 1)
|
|
body (tuple/slice elem 2)]
|
|
(put methods (keyword (if (struct? method-name) (method-name :name) method-name))
|
|
@{:fn* true :args arg-vec :body body})
|
|
(++ i))))
|
|
(array/push result @[
|
|
{:jolt/type :symbol :ns nil :name "make-reified"}
|
|
proto-sym
|
|
methods])
|
|
result)
|
|
|
|
(def core-satisfies? (fn [proto-sym obj] false))
|
|
|
|
(def core-extends? (fn [& args] false))
|
|
(def core-implements? (fn [& args] false))
|
|
(def core-type->str (fn [& args] ""))
|
|
|
|
# ============================================================
|
|
# Additional clojure.core functions (conformance batch)
|
|
# ============================================================
|
|
|
|
(defn core-find [m k]
|
|
(cond
|
|
(phm? m) (if (phm-contains? m k) [k (phm-get m k)] nil)
|
|
(or (struct? m) (table? m)) (let [v (get m k :jolt/nf)] (if (= v :jolt/nf) nil [k v]))
|
|
nil))
|
|
|
|
(defn core-keyword
|
|
"(keyword name) or (keyword ns name). Namespaced keywords are `:ns/name`.
|
|
(keyword nil) is nil; the 2-arg form requires string args (nil ns allowed)."
|
|
[& args]
|
|
(case (length args)
|
|
1 (let [a (in args 0)]
|
|
(cond
|
|
(nil? a) nil
|
|
(keyword? a) a
|
|
(or (string? a) (core-symbol? a)) (keyword (core-name a))
|
|
(error (string "keyword requires a string, symbol or keyword, got " (type a)))))
|
|
2 (let [ns (in args 0) nm (in args 1)]
|
|
(when (not (and (or (nil? ns) (string? ns)) (string? nm)))
|
|
(error "keyword ns and name must be strings"))
|
|
(keyword (if ns (string ns "/" nm) nm)))
|
|
(keyword ;args)))
|
|
|
|
(defn core-symbol
|
|
"(symbol name) or (symbol ns name) -> a jolt symbol struct. name/ns must be
|
|
strings (a single symbol arg is returned as-is)."
|
|
[& args]
|
|
(case (length args)
|
|
1 (let [a (in args 0)]
|
|
(cond
|
|
(core-symbol? a) a
|
|
(or (string? a) (keyword? a)) {:jolt/type :symbol :ns nil :name (core-name a)}
|
|
(error (string "symbol requires a string or symbol, got " (type a)))))
|
|
2 (let [ns (in args 0) nm (in args 1)]
|
|
(when (not (and (or (nil? ns) (string? ns)) (string? nm)))
|
|
(error "symbol ns and name must be strings"))
|
|
{:jolt/type :symbol :ns ns :name nm})
|
|
(error "symbol expects 1 or 2 args")))
|
|
|
|
(defn core-split-at [n coll]
|
|
(let [c (realize-for-iteration coll) m (min n (length c))]
|
|
[(tuple/slice (tuple ;(array/slice c 0 m))) (tuple/slice (tuple ;(array/slice c m)))]))
|
|
|
|
(defn core-split-with [pred coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(var i 0)
|
|
(while (and (< i (length c)) (truthy? (pred (in c i)))) (++ i))
|
|
[(tuple/slice (tuple ;(array/slice c 0 i))) (tuple/slice (tuple ;(array/slice c i)))]))
|
|
|
|
(defn- td-take-nth [n]
|
|
(fn [rf]
|
|
(var i 0)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(let [keep (= 0 (mod i n))] (++ i)
|
|
(if keep (rf (a 0) (a 1)) (a 0)))))))
|
|
(defn core-take-nth [n & rest]
|
|
(if (= 0 (length rest)) (td-take-nth n)
|
|
(let [c (realize-for-iteration (in rest 0)) r @[]]
|
|
(var i 0) (while (< i (length c)) (array/push r (in c i)) (+= i n))
|
|
(tuple/slice (tuple ;r)))))
|
|
|
|
(defn core-nthrest [coll n]
|
|
(when (not (number? n)) (error "nthrest requires a numeric count"))
|
|
(if (nil? coll) nil
|
|
(let [c (realize-for-iteration coll)
|
|
start (max 0 (min n (length c)))] # negative n -> whole coll
|
|
(tuple/slice (tuple ;(array/slice c start))))))
|
|
|
|
(defn core-nthnext [coll n]
|
|
(when (not (number? n)) (error "nthnext requires a numeric count"))
|
|
(let [r (core-nthrest coll n)] (if (or (nil? r) (= 0 (length r))) nil r)))
|
|
|
|
(defn core-butlast [coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(if (<= (length c) 1) nil (tuple/slice (tuple ;(array/slice c 0 (- (length c) 1)))))))
|
|
|
|
(defn core-filterv [pred coll]
|
|
(def pred (as-fn pred))
|
|
(let [r @[]] (each x (realize-for-iteration coll) (when (truthy? (pred x)) (array/push r x)))
|
|
(make-vec r)))
|
|
|
|
(defn core-mapv [f & colls]
|
|
(def f (as-fn f))
|
|
(let [r @[]]
|
|
(if (= 1 (length colls))
|
|
(each x (realize-for-iteration (colls 0)) (array/push r (f x)))
|
|
(let [cs (map realize-for-iteration colls)
|
|
n (min ;(map length cs))]
|
|
(var i 0) (while (< i n) (array/push r (apply f (map (fn [c] (in c i)) cs))) (++ i))))
|
|
(make-vec r)))
|
|
|
|
(defn- td-interpose [sep]
|
|
(fn [rf]
|
|
(var started false)
|
|
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
|
|
(if started (rf (rf (a 0) sep) (a 1))
|
|
(do (set started true) (rf (a 0) (a 1))))))))
|
|
(defn core-interpose [sep & rest]
|
|
(if (= 0 (length rest)) (td-interpose sep)
|
|
(let [items (realize-for-iteration (in rest 0)) r @[]]
|
|
(var first? true)
|
|
(each x items (if first? (set first? false) (array/push r sep)) (array/push r x))
|
|
(tuple ;r))))
|
|
|
|
(defn core-some-search
|
|
"(some pred coll) — first truthy (pred x), else nil."
|
|
[pred coll]
|
|
(def pred (as-fn pred))
|
|
(var result nil)
|
|
(each x (realize-for-iteration coll)
|
|
(let [r (pred x)] (when (truthy? r) (set result r) (break))))
|
|
result)
|
|
|
|
(defn core-keep
|
|
"(keep f coll) — (f x) for each x, dropping nils. (keep f) is a transducer."
|
|
[f & rest]
|
|
(def f (as-fn f))
|
|
(if (= 0 (length rest))
|
|
(td-keep f)
|
|
(let [r @[]]
|
|
(each x (realize-for-iteration (in rest 0))
|
|
(let [v (f x)] (when (not (nil? v)) (array/push r v))))
|
|
(tuple ;r))))
|
|
|
|
(defn core-interleave
|
|
"(interleave & colls) — take one from each in turn until the shortest ends."
|
|
[& colls]
|
|
(if (= 0 (length colls)) (tuple)
|
|
(let [cs (map realize-for-iteration colls)
|
|
n (min ;(map length cs))
|
|
r @[]]
|
|
(var i 0)
|
|
(while (< i n) (each c cs (array/push r (in c i))) (++ i))
|
|
(tuple ;r))))
|
|
|
|
(defn core-flatten
|
|
"(flatten coll) — fully flatten nested sequentials into one seq."
|
|
[coll]
|
|
(def r @[])
|
|
(defn seqish? [x] (or (tuple? x) (array? x) (pvec? x) (lazy-seq? x)))
|
|
(defn step [x] (each e (realize-for-iteration x) (if (seqish? e) (step e) (array/push r e))))
|
|
(when (seqish? coll) (step coll))
|
|
(tuple ;r))
|
|
|
|
(defn core-empty [coll]
|
|
(cond
|
|
(phm? coll) (make-phm)
|
|
(set? coll) (make-phs)
|
|
(plist? coll) EMPTY-PLIST
|
|
(pvec? coll) (make-vec @[])
|
|
(struct? coll) (struct)
|
|
(tuple? coll) (make-vec @[])
|
|
(array? coll) @[]
|
|
(table? coll) @{}
|
|
nil))
|
|
|
|
(defn core-not-empty [coll]
|
|
(if (or (nil? coll) (= 0 (core-count coll))) nil coll))
|
|
|
|
# rseq is defined only on vectors and sorted collections (Reversible).
|
|
(defn core-rseq [coll]
|
|
(cond
|
|
(pvec? coll) (tuple/slice (tuple ;(reverse (pv->array coll))))
|
|
(core-sorted-map? coll) (tuple/slice (tuple ;(reverse (sorted-map-entries coll))))
|
|
(core-sorted-set? coll) (tuple/slice (tuple ;(reverse (coll :items))))
|
|
(error (string "rseq requires a vector or sorted collection, got " (type coll)))))
|
|
|
|
(defn core-shuffle [coll]
|
|
(when (not (core-coll? coll)) (error (string "shuffle requires a collection, got " (type coll))))
|
|
(let [c (array/slice (realize-for-iteration coll))]
|
|
(var i (- (length c) 1))
|
|
(while (> i 0)
|
|
(let [j (math/floor (* (math/random) (+ i 1)))
|
|
tmp (in c i)]
|
|
(put c i (in c j)) (put c j tmp))
|
|
(-- i))
|
|
(tuple/slice (tuple ;c))))
|
|
|
|
(defn core-replace [smap coll]
|
|
(let [c (realize-for-iteration coll) r @[]]
|
|
(each x c (array/push r (let [v (core-get smap x :jolt/nf)] (if (= v :jolt/nf) x v))))
|
|
(tuple/slice (tuple ;r))))
|
|
|
|
(defn core-some-fn [& preds]
|
|
(fn [& xs]
|
|
(var hit nil)
|
|
(each p preds (each x xs (when (and (nil? hit) (truthy? (p x))) (set hit (p x)))))
|
|
hit))
|
|
|
|
(defn core-sequential? [x] (or (tuple? x) (array? x) (pvec? x) (plist? x) (lazy-seq? x)))
|
|
# Associative = maps and (real) vectors only. pvec is a literal/built vector;
|
|
# tuples and lists are seq results, not associative.
|
|
(defn core-associative? [x]
|
|
(or (pvec? x) (phm? x) (core-sorted-map? x)
|
|
(and (struct? x) (nil? (get x :jolt/type)))))
|
|
(defn core-ifn? [x]
|
|
(or (function? x) (cfunction? x) (keyword? x) (phm? x) (set? x) (tuple? x) (array? x) (pvec? x)
|
|
(and (struct? x) (= :symbol (x :jolt/type)))))
|
|
(defn core-indexed? [x] (or (tuple? x) (array? x) (pvec? x)))
|
|
|
|
(defn core-distinct? [& xs]
|
|
(var seen @{}) (var ok true)
|
|
(each x xs (if (get seen x) (set ok false) (put seen x true)))
|
|
ok)
|
|
|
|
# With a single item, Clojure returns it WITHOUT calling f. On ties, the last
|
|
# extremal item wins (>=/<= update), matching Clojure.
|
|
# Clojure's min-key/max-key: the 2-arg base compares with strict < / > (so the
|
|
# second wins on ties/NaN), and each further item switches on <= / >=. This
|
|
# asymmetry reproduces the JVM's NaN-ordering behavior. Janet's < / > are used
|
|
# directly (NaN comparisons are false, never throwing).
|
|
# keys must be numbers (NaN allowed) — like Clojure, which compares them with </>.
|
|
(defn core-min-key [f & xs]
|
|
(def f (as-fn f))
|
|
(when (= 0 (length xs)) (error "min-key requires at least one value"))
|
|
(if (= 1 (length xs)) (first xs)
|
|
(do (var v (in xs 0)) (var kv (need-num (f v) "min-key"))
|
|
(let [y (in xs 1) ky (need-num (f y) "min-key")] (when (not (< kv ky)) (set v y) (set kv ky)))
|
|
(var i 2)
|
|
(while (< i (length xs))
|
|
(let [w (in xs i) kw (need-num (f w) "min-key")] (when (<= kw kv) (set v w) (set kv kw)))
|
|
(++ i))
|
|
v)))
|
|
|
|
(defn core-max-key [f & xs]
|
|
(def f (as-fn f))
|
|
(when (= 0 (length xs)) (error "max-key requires at least one value"))
|
|
(if (= 1 (length xs)) (first xs)
|
|
(do (var v (in xs 0)) (var kv (need-num (f v) "max-key"))
|
|
(let [y (in xs 1) ky (need-num (f y) "max-key")] (when (not (> kv ky)) (set v y) (set kv ky)))
|
|
(var i 2)
|
|
(while (< i (length xs))
|
|
(let [w (in xs i) kw (need-num (f w) "max-key")] (when (>= kw kv) (set v w) (set kv kw)))
|
|
(++ i))
|
|
v)))
|
|
|
|
(defn core-not-every? [pred coll]
|
|
(def pred (as-fn pred))
|
|
(not (do (var ok true) (each x (realize-for-iteration coll) (when (not (truthy? (pred x))) (set ok false))) ok)))
|
|
|
|
(defn core-not-any? [pred coll]
|
|
(def pred (as-fn pred))
|
|
(do (var none true) (each x (realize-for-iteration coll) (when (truthy? (pred x)) (set none false))) none))
|
|
|
|
(defn core-vary-meta [obj f & args]
|
|
(let [m (core-meta obj)] (core-with-meta obj (apply f m args))))
|
|
|
|
# Exceptions (ex-info / ex-data / ex-message)
|
|
(defn core-ex-info [msg data & more]
|
|
@{:jolt/type :jolt/ex-info :message msg :data data
|
|
:cause (if (> (length more) 0) (in more 0) nil)})
|
|
(defn core-ex-info? [x] (and (table? x) (= :jolt/ex-info (x :jolt/type))))
|
|
(defn- unwrap-ex [e]
|
|
(if (and (or (table? e) (struct? e)) (= :jolt/exception (get e :jolt/type))) (get e :value) e))
|
|
(defn core-ex-data [e]
|
|
(let [e (unwrap-ex e)] (if (core-ex-info? e) (e :data) nil)))
|
|
(defn core-ex-message [e]
|
|
(let [e (unwrap-ex e)]
|
|
(cond (core-ex-info? e) (e :message) (string? e) e nil)))
|
|
|
|
# String split/replace that accept either a literal string or a regex value.
|
|
(defn core-str-split [pat s]
|
|
(if (regex? pat)
|
|
(re-split pat s)
|
|
(string/split pat s)))
|
|
(defn core-str-replace-all [pat repl s]
|
|
(if (regex? pat)
|
|
(re-replace-all pat s repl)
|
|
(string/replace-all pat repl s)))
|
|
(defn core-str-replace-first [pat repl s]
|
|
(if (regex? pat)
|
|
(re-replace-first pat s repl)
|
|
(string/replace pat repl s)))
|
|
|
|
(defn core-prn-str [& xs] (string (apply core-pr-str xs) "\n"))
|
|
(defn core-println-str [& xs]
|
|
(var parts @[]) (each x xs (array/push parts (str-render-one x)))
|
|
(string (string/join parts " ") "\n"))
|
|
|
|
# Iterator/enumeration seqs — Jolt has no Java iterators, so adapt to plain seq.
|
|
(defn core-enumeration-seq [x] (core-seq x))
|
|
(defn core-iterator-seq [x] (core-seq x))
|
|
(defn core-xml-seq [root]
|
|
(def out @[])
|
|
(defn walk [n]
|
|
(array/push out n)
|
|
(when (and (core-map? n) (core-contains? n :content))
|
|
(each c (realize-for-iteration (core-get n :content)) (walk c))))
|
|
(walk root)
|
|
(tuple ;out))
|
|
(defn core-line-seq [rdr]
|
|
(if (string? rdr) (core-seq (string/split "\n" rdr)) nil))
|
|
(defn core-re-matcher [re s] @{:jolt/type :jolt/matcher :re re :s s :pos 0})
|
|
|
|
# JVM reflection / proxies — not applicable on a Janet host; resolve-only.
|
|
(defn core-bean [x] (if (core-map? x) x {}))
|
|
(defn core-print-method [x writer] nil)
|
|
(defn core-print-dup [x writer] nil)
|
|
(defn core-proxy-call-with-super [f proxy meth] (f))
|
|
(defn core-proxy-mappings [proxy] {})
|
|
(defn core-update-proxy [proxy mappings] proxy)
|
|
(defn core-numeric= [& args]
|
|
(if (< (length args) 2) true
|
|
(do (var ok true) (var i 0)
|
|
(while (and ok (< i (dec (length args))))
|
|
(unless (= (in args i) (in args (+ i 1))) (set ok false)) (++ i))
|
|
ok)))
|
|
(defn core-print-str [& xs]
|
|
(var parts @[]) (each x xs (array/push parts (str-render-one x)))
|
|
(string/join parts " "))
|
|
(defn core-memfn [& args] (error "memfn: JVM method handles are not supported in Jolt"))
|
|
(defn core-seq-to-map-for-destructuring [s]
|
|
# used by {:keys [...]} destructuring over a seq of k/v pairs
|
|
(if (core-sequential? s)
|
|
(let [items (realize-for-iteration s) m @{}]
|
|
(var i 0)
|
|
(while (< (+ i 1) (length items)) (put m (in items i) (in items (+ i 1))) (+= i 2))
|
|
(table/to-struct m))
|
|
s))
|
|
(defn core-eduction [& args]
|
|
# (eduction xform* coll): apply the composed transducers eagerly to coll
|
|
(let [n (length args)
|
|
coll (in args (- n 1))
|
|
xforms (array/slice args 0 (- n 1))
|
|
xform (if (= 0 (length xforms)) (fn [rf] rf) (apply core-comp xforms))]
|
|
(core-into (make-vec @[]) xform coll)))
|
|
(defn core->Eduction [xform coll] (core-into (make-vec @[]) xform coll))
|
|
(defn core-proxy-super [& args] (error "proxy-super: JVM proxies are not supported in Jolt"))
|
|
(defn core-construct-proxy [c & args] (error "construct-proxy: not supported in Jolt"))
|
|
(defn core-init-proxy [proxy mappings] proxy)
|
|
(defn core-get-proxy-class [& interfaces] (error "get-proxy-class: not supported in Jolt"))
|
|
(defn core-undefined? [x] false)
|
|
|
|
(def- char-escapes
|
|
{10 "\\n" 9 "\\t" 13 "\\r" 12 "\\f" 8 "\\b" 34 "\\\"" 92 "\\\\"})
|
|
(def- char-names
|
|
{10 "newline" 9 "tab" 13 "return" 12 "formfeed" 8 "backspace" 32 "space"})
|
|
(defn core-char-escape-string [c]
|
|
(get char-escapes (if (core-char? c) (char-code c) c)))
|
|
(defn core-char-name-string [c]
|
|
(get char-names (if (core-char? c) (char-code c) c)))
|
|
|
|
# subseq / rsubseq over sorted collections
|
|
(defn- sorted-entries [sc]
|
|
(cond
|
|
(core-sorted-map? sc) (sorted-map-entries sc)
|
|
(core-sorted-set? sc) (map (fn [x] x) (sc :items))
|
|
(realize-for-iteration sc)))
|
|
(defn- sorted-key-of [sc e] (if (core-sorted-map? sc) (in e 0) e))
|
|
(defn core-subseq [sc & args]
|
|
(let [es (sorted-entries sc)]
|
|
(tuple ;(filter
|
|
(fn [e] (let [k (sorted-key-of sc e)]
|
|
(if (= 2 (length args))
|
|
(truthy? ((args 0) k (args 1)))
|
|
(and (truthy? ((args 0) k (args 1))) (truthy? ((args 2) k (args 3)))))))
|
|
es))))
|
|
(defn core-rsubseq [sc & args]
|
|
(tuple ;(reverse (apply core-subseq sc args))))
|
|
|
|
# ============================================================
|
|
# Additional clojure.core functions
|
|
# ============================================================
|
|
|
|
# Integer-valued: a finite number equal to its floor. Infinity floors to itself
|
|
# but is NOT integer-valued (so float?/double? are true for ##Inf, and int?/
|
|
# pos-int?/… are false), and NaN is excluded by the equality check.
|
|
(defn- intval? [x] (and (number? x) (< (math/abs x) math/inf) (= x (math/floor x))))
|
|
|
|
# Forcing lazy seqs
|
|
(defn core-doall [a & rest]
|
|
(let [coll (if (= 0 (length rest)) a (in rest 0))]
|
|
(realize-for-iteration coll) coll))
|
|
(defn core-dorun [a & rest]
|
|
(let [coll (if (= 0 (length rest)) a (in rest 0))]
|
|
(realize-for-iteration coll) nil))
|
|
(defn core-run! [f coll]
|
|
(each x (realize-for-iteration coll) (f x)) nil)
|
|
|
|
(defn core-tree-seq [branch? children root]
|
|
(def out @[])
|
|
(defn walk [node]
|
|
(array/push out node)
|
|
(when (truthy? (branch? node))
|
|
(each c (realize-for-iteration (children node)) (walk c))))
|
|
(walk root)
|
|
(tuple ;out))
|
|
|
|
# Map entries (represented as 2-element vectors)
|
|
# key/val require a map entry (a 2-element vector/tuple in Jolt); Clojure throws
|
|
# otherwise. (Jolt can't distinguish a 2-vector from a real MapEntry.)
|
|
# A map entry is a 2-element tuple — Jolt produces tuples only from map
|
|
# iteration (first/seq/map over a map), while vector literals are pvecs and
|
|
# lists are arrays. So key/val/map-entry? accept a 2-tuple and reject a plain
|
|
# vector, matching Clojure (where a MapEntry is distinct from a vector).
|
|
(defn- entry-like? [x] (and (tuple? x) (= 2 (length x))))
|
|
(defn core-key [e] (if (entry-like? e) (in e 0) (error "key requires a map entry")))
|
|
(defn core-val [e] (if (entry-like? e) (in e 1) (error "val requires a map entry")))
|
|
(defn core-map-entry? [x] (entry-like? x))
|
|
|
|
(defn core-rand-nth [coll]
|
|
(let [c (realize-for-iteration coll)]
|
|
(in c (math/floor (* (math/random) (length c))))))
|
|
|
|
(defn core-replicate [n x] (tuple ;(map (fn [_] x) (range n))))
|
|
|
|
(defn core-bounded-count [n coll]
|
|
(let [c (realize-for-iteration coll)] (min n (length c))))
|
|
|
|
(defn core-counted? [x]
|
|
(or (pvec? x) (plist? x) (phm? x) (set? x) (tuple? x) (array? x) (string? x)))
|
|
# Reversible (supports rseq) = vectors and sorted collections.
|
|
(defn core-reversible? [x] (or (pvec? x) (core-sorted-map? x) (core-sorted-set? x)))
|
|
(defn core-seqable? [x]
|
|
(or (nil? x) (tuple? x) (array? x) (pvec? x) (plist? x) (phm? x) (set? x)
|
|
(struct? x) (lazy-seq? x) (string? x)
|
|
(and (table? x) (or (get x :jolt/type) (get x :jolt/deftype)))))
|
|
|
|
# Numeric predicates (Jolt has no ratios/bigdec, so those are always false)
|
|
(defn core-nat-int? [x] (and (intval? x) (>= x 0)))
|
|
(defn core-pos-int? [x] (and (intval? x) (> x 0)))
|
|
(defn core-neg-int? [x] (and (intval? x) (< x 0)))
|
|
(defn core-double? [x] (and (number? x) (not (intval? x))))
|
|
(defn core-float? [x] (and (number? x) (not (intval? x))))
|
|
(defn core-ratio? [x] false)
|
|
(defn core-decimal? [x] false)
|
|
(defn core-rational? [x] (intval? x))
|
|
(defn core-infinite? [x] (and (number? x) (= (math/abs x) math/inf)))
|
|
(defn core-NaN? [x] (if (number? x) (not= x x) (error "NaN? requires a number")))
|
|
# Jolt has no ratio type, so numerator/denominator have no valid input (Clojure
|
|
# requires a Ratio and throws otherwise).
|
|
(defn core-numerator [x] (error "numerator requires a ratio (Jolt has no ratios)"))
|
|
(defn core-denominator [x] (error "denominator requires a ratio (Jolt has no ratios)"))
|
|
|
|
(defn core-list* [& args]
|
|
(let [n (length args)]
|
|
(if (= 0 n) nil
|
|
(let [head (array/slice args 0 (- n 1))
|
|
tail (realize-for-iteration (in args (- n 1)))]
|
|
(var r (if (array? tail) tail (array ;tail)))
|
|
(var i (- (length head) 1))
|
|
(while (>= i 0) (set r (pl-cons (in head i) r)) (-- i))
|
|
r))))
|
|
|
|
(def- special-syms
|
|
{"if" true "do" true "let*" true "fn*" true "quote" true "var" true "def" true
|
|
"loop*" true "recur" true "throw" true "try" true "catch" true "finally" true
|
|
"new" true "set!" true "." true "monitor-enter" true "monitor-exit" true})
|
|
(defn core-special-symbol? [x]
|
|
(and (core-symbol? x) (= true (get special-syms (x :name)))))
|
|
|
|
(defn core-record? [x] (and (table? x) (not (nil? (get x :jolt/deftype)))))
|
|
|
|
# Promise: single-threaded box backed by an atom (deref returns nil until set).
|
|
(defn core-promise [] (core-atom nil))
|
|
(defn core-deliver [p v] (core-reset! p v) p)
|
|
|
|
(defn core-comparator [pred]
|
|
(fn [a b] (cond (truthy? (pred a b)) -1 (truthy? (pred b a)) 1 true 0)))
|
|
(defn core-completing [rf & cf]
|
|
(let [c (if (> (length cf) 0) (in cf 0) (fn [x] x))]
|
|
(fn [& a] (case (length a) 0 (rf) 1 (c (in a 0)) (rf (in a 0) (in a 1))))))
|
|
(defn core-keyword-identical? [a b] (= a b))
|
|
(defn core-object? [x] false)
|
|
(defn core-tagged-literal [tag form] @{:jolt/type :jolt/tagged-literal :tag tag :form form})
|
|
(defn core-ensure-reduced [x] (if (core-reduced? x) x (core-reduced x)))
|
|
(defn core-halt-when [pred & rest]
|
|
(let [retf (if (> (length rest) 0) (in rest 0) nil)]
|
|
(fn [rf]
|
|
(fn [& a]
|
|
(case (length a)
|
|
0 (rf)
|
|
1 (rf (in a 0))
|
|
(if (truthy? (pred (in a 1)))
|
|
(core-reduced (if retf (retf (rf (in a 0)) (in a 1)) (in a 1)))
|
|
(rf (in a 0) (in a 1))))))))
|
|
(defn core-re-groups [m] (error "re-groups: stateful matchers are not supported in Jolt"))
|
|
|
|
# Transients — real mutable scratch collections backed by Janet's native arrays
|
|
# and tables (host interop): O(1) conj!/assoc!/dissoc!/disj!/pop!, frozen back to
|
|
# a persistent value by persistent!. A transient is a tagged table holding either
|
|
# a Janet array (vectors) or a Janet table keyed by canonical key (maps/sets, so
|
|
# collection keys still compare by value). The mutating ops return the transient.
|
|
(defn core-transient [coll]
|
|
(cond
|
|
(pvec? coll)
|
|
@{:jolt/type :jolt/transient :kind :vector :arr (pv->array coll)}
|
|
(set? coll)
|
|
(let [t @{}] (each e (phs-seq coll) (put t (canon-key e) e))
|
|
@{:jolt/type :jolt/transient :kind :set :tbl t})
|
|
(or (phm? coll) (and (struct? coll) (nil? (get coll :jolt/type))))
|
|
(let [t @{}]
|
|
(each pair (realize-for-iteration coll)
|
|
(put t (canon-key (in pair 0)) @[(in pair 0) (in pair 1)]))
|
|
@{:jolt/type :jolt/transient :kind :map :tbl t})
|
|
# mutable-build arrays (vectors/lists) — copy into a transient vector
|
|
(array? coll) @{:jolt/type :jolt/transient :kind :vector :arr (array/slice coll)}
|
|
(error (string "Don't know how to create a transient from " (type coll)))))
|
|
|
|
# A transient is invalidated by persistent!; using it afterwards is a bug.
|
|
(defn- tr-check-active! [t]
|
|
(when (get t :jolt/persistent)
|
|
(error "Transient used after persistent! call")))
|
|
|
|
(defn- tr-conj! [t x]
|
|
(tr-check-active! t)
|
|
(case (t :kind)
|
|
:vector (array/push (t :arr) x)
|
|
:set (put (t :tbl) (canon-key x) x)
|
|
:map (cond
|
|
# a [k v] pair (map-entry / 2-vector)
|
|
(and (or (pvec? x) (tuple? x) (array? x))
|
|
(= 2 (if (pvec? x) (pv-count x) (length x))))
|
|
(put (t :tbl) (canon-key (vnth x 0)) @[(vnth x 0) (vnth x 1)])
|
|
# a map: merge all its entries
|
|
(or (phm? x) (and (struct? x) (nil? (get x :jolt/type))))
|
|
(each k (if (phm? x) (keys (phm-to-struct x)) (keys x))
|
|
(put (t :tbl) (canon-key k) @[k (if (phm? x) (phm-get x k) (in x k))]))
|
|
(error "conj! on a transient map requires a [key value] pair or a map")))
|
|
t)
|
|
|
|
(defn- tr-assoc! [t k v]
|
|
(tr-check-active! t)
|
|
(case (t :kind)
|
|
:vector (let [a (t :arr)]
|
|
(when (not (and (number? k) (= k (math/floor k)) (>= k 0) (<= k (length a))))
|
|
(error (string "Index " k " out of bounds for assoc! on a transient vector of length " (length a))))
|
|
(if (= k (length a)) (array/push a v) (put a k v)))
|
|
:map (put (t :tbl) (canon-key k) @[k v])
|
|
(error "assoc! expects a transient vector or map"))
|
|
t)
|
|
|
|
# The bang ops require a transient (Clojure throws otherwise); no lenient
|
|
# fallback to the persistent op.
|
|
(defn core-conj! [& args]
|
|
(cond
|
|
(= 0 (length args)) (core-transient (make-vec @[])) # (conj!) -> (transient [])
|
|
(= 1 (length args)) (first args) # (conj! coll) -> coll, as-is
|
|
(let [t (first args) xs (tuple/slice args 1)]
|
|
(if (core-transient? t)
|
|
(do (each x xs (tr-conj! t x)) t)
|
|
(error "conj! requires a transient")))))
|
|
|
|
(defn core-assoc! [t & kvs]
|
|
# Unlike assoc, assoc! accepts an ODD number of args — a missing final value
|
|
# is taken as nil (so (get kvs (+ i 1)) rather than (in ...), which would
|
|
# error on the dangling key).
|
|
(if (core-transient? t)
|
|
(do (var i 0) (while (< i (length kvs)) (tr-assoc! t (in kvs i) (get kvs (+ i 1))) (+= i 2)) t)
|
|
(error "assoc! requires a transient")))
|
|
|
|
(defn core-dissoc! [t & ks]
|
|
(if (and (core-transient? t) (= :map (t :kind)))
|
|
(do (tr-check-active! t) (each k ks (put (t :tbl) (canon-key k) nil)) t)
|
|
(error "dissoc! requires a transient map")))
|
|
|
|
(defn core-disj! [t & xs]
|
|
(if (and (core-transient? t) (= :set (t :kind)))
|
|
(do (tr-check-active! t) (each x xs (put (t :tbl) (canon-key x) nil)) t)
|
|
(error "disj! requires a transient set")))
|
|
|
|
(defn core-pop! [t]
|
|
(if (and (core-transient? t) (= :vector (t :kind)))
|
|
(do (tr-check-active! t)
|
|
(when (= 0 (length (t :arr))) (error "Can't pop empty vector"))
|
|
(array/pop (t :arr)) t)
|
|
(error "pop! requires a transient vector")))
|
|
|
|
(defn core-persistent! [t]
|
|
(if (core-transient? t)
|
|
(do
|
|
(tr-check-active! t)
|
|
(def result
|
|
(case (t :kind)
|
|
:vector (make-vec (t :arr))
|
|
:set (do (var s (make-phs)) (each [_ e] (pairs (t :tbl)) (set s (phs-conj s e))) s)
|
|
:map (do (var m (make-phm)) (each [_ pair] (pairs (t :tbl)) (set m (phm-assoc m (in pair 0) (in pair 1)))) m)))
|
|
# Invalidate: any further bang op (or a second persistent!) now throws.
|
|
(put t :jolt/persistent true)
|
|
result)
|
|
(error "persistent! requires a transient")))
|
|
|
|
# Unchecked arithmetic — Jolt numbers don't overflow, so these are plain ops.
|
|
(defn core-unchecked-add [a b] (+ a b))
|
|
(defn core-unchecked-subtract [a b] (- a b))
|
|
(defn core-unchecked-multiply [a b] (* a b))
|
|
(defn core-unchecked-negate [a] (- a))
|
|
(defn core-unchecked-inc [a] (+ a 1))
|
|
(defn core-unchecked-dec [a] (- a 1))
|
|
(defn core-unchecked-divide-int [a b] (math/trunc (/ a b)))
|
|
(defn core-unchecked-remainder-int [a b] (% a b))
|
|
(defn core-unchecked-int [a] (math/trunc a))
|
|
|
|
# Hashing helpers
|
|
# Hashes are masked to 24 bits at each step so intermediate products stay within
|
|
# Janet's integer range (a float here would make band error).
|
|
(defn- h24 [x] (band (hash x) 0xffffff))
|
|
(defn core-hash-combine [a b] (band (bxor (h24 a) (+ (h24 b) 0x9e3779)) 0xffffff))
|
|
(defn core-hash-ordered-coll [coll]
|
|
(var h 1) (each x (realize-for-iteration coll) (set h (band (+ (* 31 h) (h24 x)) 0xffffff))) h)
|
|
(defn core-hash-unordered-coll [coll]
|
|
(var h 0) (each x (realize-for-iteration coll) (set h (band (+ h (h24 x)) 0xffffff))) h)
|
|
|
|
(defn core-ex-cause [e] (and (table? e) (get e :cause)))
|
|
(defn core-prefers [mm-var] (or (get mm-var :jolt/prefers) {}))
|
|
|
|
(defn core-random-uuid []
|
|
(defn hx [n] (string/format "%x" (math/floor (* (math/random) n))))
|
|
(string (hx 0x10000) (hx 0x10000) "-" (hx 0x10000) "-4" (hx 0x1000)
|
|
"-" (hx 0x1000) "-" (hx 0x10000) (hx 0x10000) (hx 0x10000)))
|
|
|
|
(def- core-bindings
|
|
"Map of symbol name → function for all core functions."
|
|
@{"nil?" core-nil?
|
|
"some?" core-some?
|
|
"string?" core-string?
|
|
"number?" core-number?
|
|
"fn?" core-fn?
|
|
"keyword?" core-keyword?
|
|
"symbol?" core-symbol?
|
|
"vector?" core-vector?
|
|
"map?" core-map?
|
|
"seq?" core-seq?
|
|
"coll?" core-coll?
|
|
"true?" core-true?
|
|
"false?" core-false?
|
|
"identical?" core-identical?
|
|
"zero?" core-zero?
|
|
"pos?" core-pos?
|
|
"neg?" core-neg?
|
|
"even?" core-even?
|
|
"odd?" core-odd?
|
|
"integer?" core-integer?
|
|
"boolean?" core-boolean?
|
|
"list?" core-list?
|
|
"empty?" core-empty?
|
|
"every?" core-every?
|
|
"+" core-+
|
|
"-" core-sub
|
|
"*" core-*
|
|
"/" core-/
|
|
"inc" core-inc
|
|
"dec" core-dec
|
|
# auto-promoting variants — Jolt numbers don't overflow, so these are the
|
|
# same as their non-quoted counterparts
|
|
"+'" core-+
|
|
"-'" core-sub
|
|
"*'" core-*
|
|
"inc'" core-inc
|
|
"dec'" core-dec
|
|
"mod" core-mod
|
|
"rem" core-rem
|
|
"quot" core-quot
|
|
"max" core-max
|
|
"min" core-min
|
|
"abs" core-abs
|
|
"rand" core-rand
|
|
"rand-int" core-rand-int
|
|
"=" core-=
|
|
"not=" core-not=
|
|
"<" core-<
|
|
">" core->
|
|
"<=" core-<=
|
|
">=" core->=
|
|
"conj" core-conj
|
|
"assoc" core-assoc
|
|
"dissoc" core-dissoc
|
|
"get" core-get
|
|
"get-in" core-get-in
|
|
"contains?" core-contains?
|
|
"count" core-count
|
|
"partition-all" core-partition-all
|
|
"reductions" core-reductions
|
|
"dedupe" core-dedupe
|
|
"keep-indexed" core-keep-indexed
|
|
"map-indexed" core-map-indexed
|
|
"cycle" core-cycle
|
|
"reduce-kv" core-reduce-kv
|
|
"peek" core-peek
|
|
"pop" core-pop
|
|
"subvec" core-subvec
|
|
"trampoline" core-trampoline
|
|
"format" core-format
|
|
"letfn" core-letfn
|
|
"doseq" core-doseq
|
|
"assert" core-assert
|
|
"first" core-first
|
|
"rest" core-rest
|
|
"next" core-next
|
|
"cons" core-cons
|
|
"seq" core-seq
|
|
"vec" core-vec
|
|
"into" core-into
|
|
"merge" core-merge
|
|
"merge-with" core-merge-with
|
|
"keys" core-keys
|
|
"vals" core-vals
|
|
"select-keys" core-select-keys
|
|
"with-meta" core-with-meta
|
|
"zipmap" core-zipmap
|
|
"map" core-map
|
|
"filter" core-filter
|
|
"remove" core-remove
|
|
"reduce" core-reduce
|
|
"apply" core-apply
|
|
"second" core-second
|
|
"doall" core-doall
|
|
"dorun" core-dorun
|
|
"run!" core-run!
|
|
"tree-seq" core-tree-seq
|
|
"key" core-key
|
|
"val" core-val
|
|
"map-entry?" core-map-entry?
|
|
"rand-nth" core-rand-nth
|
|
"replicate" core-replicate
|
|
"bounded-count" core-bounded-count
|
|
"counted?" core-counted?
|
|
"reversible?" core-reversible?
|
|
"seqable?" core-seqable?
|
|
"nat-int?" core-nat-int?
|
|
"pos-int?" core-pos-int?
|
|
"neg-int?" core-neg-int?
|
|
"double?" core-double?
|
|
"float?" core-float?
|
|
"ratio?" core-ratio?
|
|
"decimal?" core-decimal?
|
|
"rational?" core-rational?
|
|
"infinite?" core-infinite?
|
|
"NaN?" core-NaN?
|
|
"numerator" core-numerator
|
|
"denominator" core-denominator
|
|
"list*" core-list*
|
|
"special-symbol?" core-special-symbol?
|
|
"record?" core-record?
|
|
"promise" core-promise
|
|
"deliver" core-deliver
|
|
"future" core-future
|
|
"future-call" core-future-call
|
|
"future?" core-future?
|
|
"future-done?" core-future-done?
|
|
"future-cancel" core-future-cancel
|
|
"future-cancelled?" core-future-cancelled?
|
|
"comparator" core-comparator
|
|
"completing" core-completing
|
|
"keyword-identical?" core-keyword-identical?
|
|
"object?" core-object?
|
|
"tagged-literal" core-tagged-literal
|
|
"ensure-reduced" core-ensure-reduced
|
|
"unreduced" core-unreduced
|
|
"halt-when" core-halt-when
|
|
"re-groups" core-re-groups
|
|
"transient" core-transient
|
|
"transient?" core-transient?
|
|
"persistent!" core-persistent!
|
|
"conj!" core-conj!
|
|
"assoc!" core-assoc!
|
|
"dissoc!" core-dissoc!
|
|
"pop!" core-pop!
|
|
"unchecked-add" core-unchecked-add
|
|
"unchecked-add-int" core-unchecked-add
|
|
"unchecked-subtract" core-unchecked-subtract
|
|
"unchecked-subtract-int" core-unchecked-subtract
|
|
"unchecked-multiply" core-unchecked-multiply
|
|
"unchecked-multiply-int" core-unchecked-multiply
|
|
"unchecked-negate" core-unchecked-negate
|
|
"unchecked-negate-int" core-unchecked-negate
|
|
"unchecked-inc" core-unchecked-inc
|
|
"unchecked-inc-int" core-unchecked-inc
|
|
"unchecked-dec" core-unchecked-dec
|
|
"unchecked-dec-int" core-unchecked-dec
|
|
"unchecked-divide-int" core-unchecked-divide-int
|
|
"unchecked-remainder-int" core-unchecked-remainder-int
|
|
"unchecked-int" core-unchecked-int
|
|
"unchecked-long" core-unchecked-int
|
|
"hash-combine" core-hash-combine
|
|
"hash-ordered-coll" core-hash-ordered-coll
|
|
"hash-unordered-coll" core-hash-unordered-coll
|
|
"ex-cause" core-ex-cause
|
|
"prefers" core-prefers
|
|
"random-uuid" core-random-uuid
|
|
"last" core-last
|
|
"drop-last" core-drop-last
|
|
"take-last" core-take-last
|
|
"interpose" core-interpose
|
|
"mapcat" core-mapcat
|
|
"some" core-some-search
|
|
"keep" core-keep
|
|
"interleave" core-interleave
|
|
"flatten" core-flatten
|
|
"every-pred" core-every-pred
|
|
"find" core-find
|
|
"transduce" core-transduce
|
|
"sequence" core-sequence
|
|
"eduction" core-sequence
|
|
"unreduced" core-unreduced
|
|
"keyword" core-keyword
|
|
"symbol" core-symbol
|
|
"namespace" core-namespace
|
|
"sorted-map" core-sorted-map
|
|
"sorted-set" core-sorted-set
|
|
"sorted?" core-sorted-map?
|
|
"reduced" core-reduced
|
|
"reduced?" core-reduced?
|
|
"split-at" core-split-at
|
|
"split-with" core-split-with
|
|
"take-nth" core-take-nth
|
|
"nthrest" core-nthrest
|
|
"nthnext" core-nthnext
|
|
"butlast" core-butlast
|
|
"filterv" core-filterv
|
|
"mapv" core-mapv
|
|
"empty" core-empty
|
|
"not-empty" core-not-empty
|
|
"rseq" core-rseq
|
|
"shuffle" core-shuffle
|
|
"replace" core-replace
|
|
"some-fn" core-some-fn
|
|
"sequential?" core-sequential?
|
|
"associative?" core-associative?
|
|
"ifn?" core-ifn?
|
|
"indexed?" core-indexed?
|
|
"distinct?" core-distinct?
|
|
"min-key" core-min-key
|
|
"max-key" core-max-key
|
|
"not-every?" core-not-every?
|
|
"not-any?" core-not-any?
|
|
"vary-meta" core-vary-meta
|
|
"ex-info" core-ex-info
|
|
"ex-data" core-ex-data
|
|
"ex-message" core-ex-message
|
|
"prn-str" core-prn-str
|
|
"println-str" core-println-str
|
|
"volatile?" core-volatile?
|
|
"force" core-force
|
|
"realized?" core-realized?
|
|
"delay?" core-delay?
|
|
"make-delay" core-make-delay
|
|
"delay" core-delay
|
|
"take" core-take
|
|
"drop" core-drop
|
|
"take-while" core-take-while
|
|
"drop-while" core-drop-while
|
|
"concat" core-concat
|
|
"reverse" core-reverse
|
|
"nth" core-nth
|
|
"sort" core-sort
|
|
"sort-by" core-sort-by
|
|
"distinct" core-distinct
|
|
"group-by" core-group-by
|
|
"frequencies" core-frequencies
|
|
"partition" core-partition
|
|
"partition-by" core-partition-by
|
|
"range" core-range
|
|
"repeat" core-repeat
|
|
"iterate" core-iterate
|
|
"repeatedly" core-repeatedly
|
|
"identity" core-identity
|
|
"constantly" core-constantly
|
|
"complement" core-complement
|
|
"comp" core-comp
|
|
"partial" core-partial
|
|
"juxt" core-juxt
|
|
"memoize" core-memoize
|
|
"vector" core-vector
|
|
"hash-map" core-hash-map
|
|
"array-map" core-array-map
|
|
"hash-set" core-hash-set
|
|
"set" core-set
|
|
"list" core-list
|
|
"set?" core-set?
|
|
"disj" core-disj
|
|
"lazy-seq" core-lazy-seq
|
|
"lazy-cat" core-lazy-cat
|
|
"coll->cells" coll->cells
|
|
"make-lazy-seq" make-lazy-seq
|
|
"str" core-str
|
|
"name" core-name
|
|
"subs" core-subs
|
|
"str-trim" string/trim
|
|
"str-upper" string/ascii-upper
|
|
"str-lower" string/ascii-lower
|
|
"str-find" string/find
|
|
"str-replace" core-str-replace-first
|
|
"str-replace-all" core-str-replace-all
|
|
"str-reverse-b" string/reverse
|
|
"str-join" core-str-join
|
|
"str-split" core-str-split
|
|
"re-pattern" re-pattern
|
|
"re-find" re-find
|
|
"re-matches" re-matches
|
|
"re-seq" re-seq
|
|
"regex?" regex?
|
|
"str-triml" string/triml
|
|
"str-trimr" string/trimr
|
|
"print" core-print
|
|
"println" core-println
|
|
"pr" core-pr
|
|
"prn" core-prn
|
|
"pr-str" core-pr-str
|
|
# Java-style arrays (buffers for bytes, arrays otherwise)
|
|
"alength" core-alength
|
|
"aget" core-aget
|
|
"aset" core-aset
|
|
"aclone" core-aclone
|
|
"object-array" core-object-array
|
|
"int-array" core-int-array
|
|
"long-array" core-long-array
|
|
"short-array" core-short-array
|
|
"double-array" core-double-array
|
|
"float-array" core-float-array
|
|
"char-array" core-char-array
|
|
"boolean-array" core-boolean-array
|
|
"byte-array" core-byte-array
|
|
"aset-byte" core-aset-byte
|
|
"aset-int" core-aset-int
|
|
"aset-long" core-aset-long
|
|
"aset-short" core-aset-short
|
|
"aset-double" core-aset-double
|
|
"aset-float" core-aset-float
|
|
"aset-char" core-aset-char
|
|
"aset-boolean" core-aset-boolean
|
|
"make-array" core-make-array
|
|
"into-array" core-into-array
|
|
"to-array" core-to-array
|
|
"to-array-2d" core-to-array-2d
|
|
"bytes" core-bytes
|
|
"booleans" core-booleans
|
|
"ints" core-ints
|
|
"longs" core-longs
|
|
"shorts" core-shorts
|
|
"doubles" core-doubles
|
|
"floats" core-floats
|
|
"chars" core-chars
|
|
"byte" core-byte
|
|
"short" core-short
|
|
"unchecked-byte" core-unchecked-byte
|
|
"unchecked-short" core-unchecked-short
|
|
"unchecked-char" core-unchecked-char
|
|
"unchecked-float" core-unchecked-float
|
|
"unchecked-double" core-unchecked-double
|
|
"bigint" core-bigint
|
|
"biginteger" core-biginteger
|
|
"bigdec" core-bigdec
|
|
"chunk-buffer" core-chunk-buffer
|
|
"chunk-append" core-chunk-append
|
|
"chunk" core-chunk
|
|
"chunked-seq?" core-chunked-seq?
|
|
"chunk-first" core-chunk-first
|
|
"chunk-rest" core-chunk-rest
|
|
"chunk-next" core-chunk-next
|
|
"chunk-cons" core-chunk-cons
|
|
"boolean" core-boolean
|
|
"cat" core-cat
|
|
"disj!" core-disj!
|
|
"rationalize" core-rationalize
|
|
"random-sample" core-random-sample
|
|
"reader-conditional" core-reader-conditional
|
|
"reader-conditional?" core-reader-conditional?
|
|
"sorted-map-by" core-sorted-map-by
|
|
"sorted-set-by" core-sorted-set-by
|
|
"array-seq" core-array-seq
|
|
"seque" core-seque
|
|
"supers" core-supers
|
|
"class" core-class
|
|
"clojure-version" core-clojure-version
|
|
"munge" core-munge
|
|
"namespace-munge" core-namespace-munge
|
|
"test" core-test
|
|
"enumeration-seq" core-enumeration-seq
|
|
"iterator-seq" core-iterator-seq
|
|
"xml-seq" core-xml-seq
|
|
"line-seq" core-line-seq
|
|
"re-matcher" core-re-matcher
|
|
"bean" core-bean
|
|
"print-method" core-print-method
|
|
"print-dup" core-print-dup
|
|
"proxy-call-with-super" core-proxy-call-with-super
|
|
"proxy-mappings" core-proxy-mappings
|
|
"update-proxy" core-update-proxy
|
|
"==" core-numeric=
|
|
"print-str" core-print-str
|
|
"memfn" core-memfn
|
|
"seq-to-map-for-destructuring" core-seq-to-map-for-destructuring
|
|
"eduction" core-eduction
|
|
"->Eduction" core->Eduction
|
|
"proxy-super" core-proxy-super
|
|
"construct-proxy" core-construct-proxy
|
|
"init-proxy" core-init-proxy
|
|
"get-proxy-class" core-get-proxy-class
|
|
"undefined?" core-undefined?
|
|
"char-escape-string" core-char-escape-string
|
|
"char-name-string" core-char-name-string
|
|
"subseq" core-subseq
|
|
"rsubseq" core-rsubseq
|
|
# Bit operations
|
|
"bit-and" core-bit-and
|
|
"bit-or" core-bit-or
|
|
"bit-xor" core-bit-xor
|
|
"bit-not" core-bit-not
|
|
"bit-shift-left" core-bit-shift-left
|
|
"bit-shift-right" core-bit-shift-right
|
|
"bit-clear" core-bit-clear
|
|
"bit-set" core-bit-set
|
|
"bit-flip" core-bit-flip
|
|
"bit-test" core-bit-test
|
|
"bit-and-not" core-bit-and-not
|
|
"unsigned-bit-shift-right" core-unsigned-bit-shift-right
|
|
# Integer coercion / unchecked math
|
|
"int" core-int
|
|
"long" core-long
|
|
"double" core-double
|
|
"float" core-float
|
|
"num" core-num
|
|
"char" core-char
|
|
"char?" core-char?
|
|
"unchecked-inc" core-unchecked-inc
|
|
"unchecked-dec" core-unchecked-dec
|
|
"unchecked-add" core-unchecked-add
|
|
"unchecked-subtract" core-unchecked-subtract
|
|
# Hash
|
|
"hash" core-hash
|
|
"atom" core-atom
|
|
"atom?" core-atom?
|
|
"deref" core-deref
|
|
"reset!" core-reset!
|
|
"swap!" core-swap!
|
|
"swap-vals!" core-swap-vals!
|
|
"reset-vals!" core-reset-vals!
|
|
"compare-and-set!" core-compare-and-set!
|
|
"set-validator!" core-set-validator!
|
|
"get-validator" core-get-validator
|
|
"add-watch" core-add-watch
|
|
"remove-watch" core-remove-watch
|
|
"not" core-not
|
|
"and" core-and
|
|
"or" core-or
|
|
"cond" core-cond
|
|
"case" core-case
|
|
"for" core-for
|
|
"when" core-when
|
|
"when-not" core-when-not
|
|
"if-not" core-if-not
|
|
"when-first" core-when-first
|
|
"if-let" core-if-let
|
|
"when-let" core-when-let
|
|
"if-some" core-if-some
|
|
"when-some" core-when-some
|
|
"doto" core-doto
|
|
"condp" core-condp
|
|
"dotimes" core-dotimes
|
|
"while" core-while
|
|
"->" core-thread-first
|
|
"->>" core-thread-last
|
|
"some->" core-some->
|
|
"some->>" core-some->>
|
|
"cond->" core-cond->
|
|
"cond->>" core-cond->>
|
|
"as->" core-as->
|
|
"defn" core-defn
|
|
"defn-" core-defn-
|
|
"derive" core-derive
|
|
"isa?" core-isa?
|
|
"parents" core-parents
|
|
"ancestors" core-ancestors
|
|
"descendants" core-descendants
|
|
"make-hierarchy" core-make-hierarchy
|
|
"underive" core-underive
|
|
"get-method" core-get-method
|
|
"methods" core-methods
|
|
"remove-method" core-remove-method
|
|
"remove-all-methods" core-remove-all-methods
|
|
"prefer-method" core-prefer-method
|
|
"Object" core-Object
|
|
"declare" core-declare
|
|
"fn" core-fn
|
|
"let" core-let
|
|
"loop" core-loop
|
|
"defprotocol" core-defprotocol
|
|
"extend-type" core-extend-type
|
|
"extend-protocol" core-extend-protocol
|
|
"extend" core-extend
|
|
"reify" core-reify
|
|
"satisfies?" core-satisfies?
|
|
"extends?" core-extends?
|
|
"implements?" core-implements?
|
|
"type->str" core-type->str
|
|
"volatile!" core-volatile!
|
|
"vswap!" core-vswap!
|
|
"vreset!" core-vreset!
|
|
"proxy" core-proxy
|
|
"Thread" core-Thread
|
|
"ThreadLocal" core-ThreadLocal
|
|
"IllegalStateException" core-IllegalStateException
|
|
"definterface" core-definterface
|
|
"defrecord" core-defrecord
|
|
"comment" core-comment
|
|
"resolve" core-resolve
|
|
"ns-name" core-ns-name
|
|
"update" core-update
|
|
"update-in" core-update-in
|
|
"assoc-in" core-assoc-in
|
|
"fnil" core-fnil
|
|
"copy-core-var" core-copy-core-var
|
|
"copy-var" core-copy-var
|
|
"macrofy" core-macrofy
|
|
"new-var" core-new-var
|
|
"avoid-method-too-large" core-avoid-method-too-large
|
|
"qualified-symbol?" core-qualified-symbol?
|
|
"simple-symbol?" core-simple-symbol?
|
|
"qualified-keyword?" core-qualified-keyword?
|
|
"simple-keyword?" core-simple-keyword?
|
|
"ident?" core-ident?
|
|
"qualified-ident?" core-qualified-ident?
|
|
"simple-ident?" core-simple-ident?
|
|
"inst?" core-inst?
|
|
"inst-ms" core-inst-ms
|
|
"uri?" core-uri?
|
|
"uuid?" core-uuid?
|
|
"bytes?" core-bytes?
|
|
"tagged-literal?" core-tagged-literal?
|
|
"meta" core-meta
|
|
"var-get" core-var-get
|
|
"var-set" core-var-set
|
|
"var?" core-var?
|
|
"var-dynamic?" core-var-dynamic?
|
|
"alter-var-root" core-alter-var-root
|
|
"alter-meta!" core-alter-meta!
|
|
"reset-meta!" core-reset-meta!
|
|
"intern" core-intern
|
|
"binding" core-binding
|
|
"push-thread-bindings" core-push-thread-bindings
|
|
"pop-thread-bindings" core-pop-thread-bindings
|
|
# Dynamic vars — stubs for SCI bootstrap
|
|
"*unchecked-math*" false
|
|
"*clojure-version*" @{:major 1 :minor 11 :incremental 0 :qualifier nil}
|
|
"*1" :jolt/nil-sentinel
|
|
"*2" :jolt/nil-sentinel
|
|
"*3" :jolt/nil-sentinel
|
|
"*e" :jolt/nil-sentinel
|
|
"*assert" true})
|
|
|
|
(defn core-macro-names
|
|
"Set of core binding names that are macros."
|
|
[]
|
|
@{"and" true "or" true "cond" true "case" true "for" true "when" true "when-not" true "if-let" true "when-let" true "if-some" true "when-some" true "doto" true "defn" true "defn-" true "declare" true "fn" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "comment" true "binding" true "lazy-seq" true "lazy-cat" true "if-not" true "when-first" true "condp" true "dotimes" true "while" true "some->" true "some->>" true "cond->" true "cond->>" true "as->" true "->" true "->>" true "letfn" true "doseq" true "delay" true "assert" true "future" true})
|
|
|
|
(def init-core!
|
|
(fn [& args]
|
|
(case (length args)
|
|
1 (let [ctx (args 0)
|
|
ns (ctx-find-ns ctx "clojure.core")]
|
|
(loop [[name fn] :pairs core-bindings]
|
|
(def v (ns-intern ns name (if (= fn :jolt/nil-sentinel) nil fn)))
|
|
(when (get (core-macro-names) name)
|
|
(put v :macro true)))
|
|
ns)
|
|
2 (let [ctx (args 0) ns-name (args 1)
|
|
ns (ctx-find-ns ctx ns-name)]
|
|
(loop [[name fn] :pairs core-bindings]
|
|
(def v (ns-intern ns name (if (= fn :jolt/nil-sentinel) nil fn)))
|
|
(when (get (core-macro-names) name)
|
|
(put v :macro true)))
|
|
ns)
|
|
(error "Wrong number of args passed to: init-core!"))))
|