fn -> fn* is a one-line head-swap (the analyzer treats fn* as the primitive). It's in 00-syntax since the analyzer, kernel, and other overlay macro bodies all use fn. First of the fundamental macros to move. conformance 228x3, fixpoint, clojure-test-suite 3930.
160 lines
7.2 KiB
Clojure
160 lines
7.2 KiB
Clojure
;; clojure.core — syntax tier. The control macros the compiler and every later
|
|
;; tier depend on (when/cond/and/or/...), expressed as defmacro. Loaded FIRST
|
|
;; (before 00-kernel), interpreted, so the macros exist before any code that uses
|
|
;; them is compiled — including the kernel tier, the self-hosted analyzer, and the
|
|
;; seq/coll tiers.
|
|
;;
|
|
;; CONSTRAINT: a macro here may use ONLY special forms (if/do/let*/fn*/not) and
|
|
;; core-renames SEED primitives (first/next/rest/nth/count/empty?/...). It must
|
|
;; NOT use kernel-tier fns (second/peek/subvec/...) or anything defined later —
|
|
;; those don't exist yet when this tier loads.
|
|
|
|
(defmacro when [test & body]
|
|
`(if ~test (do ~@body)))
|
|
|
|
(defmacro when-not [test & body]
|
|
`(if (not ~test) (do ~@body)))
|
|
|
|
(defmacro and [& exprs]
|
|
(if (empty? exprs)
|
|
true
|
|
(if (empty? (rest exprs))
|
|
(first exprs)
|
|
`(let* [and# ~(first exprs)] (if and# (and ~@(rest exprs)) and#)))))
|
|
|
|
(defmacro or [& exprs]
|
|
(if (empty? exprs)
|
|
nil
|
|
(if (empty? (rest exprs))
|
|
(first exprs)
|
|
`(let* [or# ~(first exprs)] (if or# or# (or ~@(rest exprs)))))))
|
|
|
|
;; :else (any truthy value) is just a test, so no special case — (if :else e ...)
|
|
;; takes e.
|
|
(defmacro cond [& clauses]
|
|
(if (empty? clauses)
|
|
nil
|
|
`(if ~(first clauses) ~(nth clauses 1) (cond ~@(drop 2 clauses)))))
|
|
|
|
;; Threading: a list form threads x in as the first (->) or last (->>) arg; a bare
|
|
;; symbol becomes (form x). Recursive; the expand-once cache makes that free.
|
|
(defmacro -> [x & forms]
|
|
(if (empty? forms)
|
|
x
|
|
(let [form (first forms)
|
|
threaded (if (seq? form)
|
|
`(~(first form) ~x ~@(rest form))
|
|
`(~form ~x))]
|
|
`(-> ~threaded ~@(rest forms)))))
|
|
|
|
(defmacro ->> [x & forms]
|
|
(if (empty? forms)
|
|
x
|
|
(let [form (first forms)
|
|
threaded (if (seq? form)
|
|
`(~(first form) ~@(rest form) ~x)
|
|
`(~form ~x))]
|
|
`(->> ~threaded ~@(rest forms)))))
|
|
|
|
;; Forward declaration is a no-op on Jolt — the compiler resolves forward refs via
|
|
;; pending cells (matching the prior Janet macro).
|
|
(defmacro declare [& syms] `(do))
|
|
|
|
;; fn -> fn*: the analyzer treats fn* as the primitive (it handles params, &-rest,
|
|
;; multi-arity); fn is just the public spelling.
|
|
(defmacro fn [& args] `(fn* ~@args))
|
|
|
|
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet symbol
|
|
;; the destructurer rejects). This defn compiles fine: by the time a tier triggers
|
|
;; the analyzer build the kernel is in place (the build is gated until then).
|
|
(defn- fresh-sym [] (symbol (str (gensym))))
|
|
|
|
;; cond->: thread expr through each (test form) pair, only when the test is truthy.
|
|
;; Linear nested let*, a distinct fresh symbol per step.
|
|
(defmacro cond-> [expr & clauses]
|
|
(let [step (fn step [prev cls]
|
|
(if (empty? cls)
|
|
prev
|
|
(let [t (first cls)
|
|
f (nth cls 1)
|
|
gn (fresh-sym)
|
|
call (if (seq? f) `(~(first f) ~prev ~@(rest f)) `(~f ~prev))]
|
|
`(let* [~gn (if ~t ~call ~prev)] ~(step gn (drop 2 cls))))))
|
|
g0 (fresh-sym)]
|
|
`(let* [~g0 ~expr] ~(step g0 clauses))))
|
|
|
|
;; case: nested =/or tests (no jump table). Test constants are NOT evaluated —
|
|
;; symbols and list constants are quoted; a list in test position is a set (or).
|
|
(defmacro case [expr & clauses]
|
|
(let [g (fresh-sym)
|
|
mk-const (fn [c] (if (or (symbol? c) (seq? c)) `(quote ~c) c))
|
|
mk-test (fn [c]
|
|
(if (seq? c)
|
|
`(or ~@(map (fn [v] `(= ~g ~(mk-const v))) c))
|
|
`(= ~g ~(mk-const c))))
|
|
build (fn build [cls]
|
|
(if (empty? cls)
|
|
nil
|
|
(if (empty? (rest cls))
|
|
(first cls)
|
|
`(if ~(mk-test (first cls)) ~(nth cls 1) ~(build (drop 2 cls))))))]
|
|
`(let* [~g ~expr] ~(build clauses))))
|
|
|
|
;; for: list comprehension, desugared to nested map/mapcat over the binding colls.
|
|
;; Per binding group: :when wraps the inner form in (if test (list inner) []) so
|
|
;; mapcat drops it when false; :let wraps it in a let*; :while wraps the coll in
|
|
;; take-while. The last group with no modifiers is a plain map (no flatten needed).
|
|
;; Faithful port of the prior Janet macro (single body expr). The body uses only
|
|
;; kernel/seed fns so it runs at analyzer-build time. `fn` (not fn*) carries the
|
|
;; binding so destructuring forms work.
|
|
(defmacro for [bindings body]
|
|
(let [scan (fn scan [bvec i bind coll mods]
|
|
(if (and (< i (count bvec)) (keyword? (nth bvec i)))
|
|
(let [k (nth bvec i)
|
|
v (nth bvec (inc i))]
|
|
(cond
|
|
(= k :when) (scan bvec (+ i 2) bind coll (conj mods [:when v]))
|
|
(= k :let) (scan bvec (+ i 2) bind coll (conj mods [:let v]))
|
|
(= k :while) (scan bvec (+ i 2) bind `(take-while (fn [~bind] ~v) ~coll) mods)
|
|
:else (scan bvec (inc i) bind coll mods)))
|
|
[i bind coll mods]))
|
|
parse-groups (fn parse-groups [bvec i groups]
|
|
(if (>= i (count bvec))
|
|
groups
|
|
(let [r (scan bvec (+ i 2) (nth bvec i) (nth bvec (inc i)) [])]
|
|
(parse-groups bvec (nth r 0)
|
|
(conj groups [(nth r 1) (nth r 2) (nth r 3)])))))
|
|
;; Apply the group's modifiers around a contribution that is ALREADY a seq
|
|
;; (a (list body) for the last group, an inner comprehension otherwise), so
|
|
;; :when just returns it or [] — no extra (list ...) that mapcat couldn't
|
|
;; flatten. :let binds around it; mods apply outer-to-inner (left to right).
|
|
wrap-mods (fn wrap-mods [mods inner]
|
|
(if (empty? mods)
|
|
inner
|
|
(let [m (first mods)
|
|
sub (wrap-mods (rest mods) inner)]
|
|
(if (= (first m) :when)
|
|
`(if ~(nth m 1) ~sub [])
|
|
`(let* ~(nth m 1) ~sub)))))
|
|
build (fn build [idx groups]
|
|
(let [g (nth groups idx)
|
|
my-bind (nth g 0)
|
|
my-coll (nth g 1)
|
|
my-mods (nth g 2)
|
|
is-last (= idx (dec (count groups)))]
|
|
(if (and is-last (empty? my-mods))
|
|
;; fast path: last group, no modifiers -> a plain map of body
|
|
`(map (fn [~my-bind] ~body) ~my-coll)
|
|
;; general: mapcat over a seq contribution (wrap a last-group
|
|
;; body in a one-element list so mapcat yields the bodies).
|
|
(let [base (if is-last `(list ~body) (build (inc idx) groups))]
|
|
`(mapcat (fn [~my-bind] ~(wrap-mods my-mods base)) ~my-coll)))))]
|
|
(if (>= (count bindings) 2)
|
|
(build 0 (parse-groups bindings 0 []))
|
|
body)))
|
|
|
|
;; doseq runs body for side effects across the bindings, returning nil. Same
|
|
;; shortcut as the prior Janet macro: realize a `for` comprehension with count
|
|
;; (for handles :when/:let/:while and multiple bindings).
|
|
(defmacro doseq [bindings & body]
|
|
`(do (count (for ~bindings (do ~@body nil))) nil))
|