letfn is a macro over a letfn* special form (Clojure semantics)
jolt modelled letfn as a special form directly, so (macroexpand-1 '(letfn …)) returned the form unchanged. Clojure's letfn is a macro that expands to letfn*, and macroexpansion tooling (tools.macro, tools.analyzer) depends on that — its special-form handlers key on letfn*, not letfn. Split it the Clojure way: - letfn* is now the special form (analyzer), taking flat name/fn-form pairs [name1 fn1 name2 fn2 …] — the letrec :let lowering is unchanged. - letfn is a macro (00-syntax) turning each (name [params] body*) spec into a name + (fn name [params] body*) binding, so it expands to letfn*. So (macroexpand-1 '(letfn [(f [x] x)] (f 1))) now yields (letfn* [f (fn f [x] x)] (f 1)), and clojure.tools.macro passes its whole suite (macrolet / symbol-macrolet / mexpand-all). Listed in docs + site. make test green (+1 corpus row, 0 new divergences), shakesmoke byte-identical. One re-mint (analyzer + the letfn macro); selfhost holds.
This commit is contained in:
parent
7891fa0d55
commit
21cd88deee
6 changed files with 341 additions and 330 deletions
|
|
@ -65,6 +65,8 @@ e.g. the [ring-app example](https://github.com/jolt-lang/examples/tree/main/ring
|
|||
byte arrays.
|
||||
* [data.priority-map](https://github.com/clojure/data.priority-map) — priority
|
||||
maps (incl. keyfn / custom comparator), with `subseq`/`rsubseq`.
|
||||
* [tools.macro](https://github.com/clojure/tools.macro) — local macros
|
||||
(`macrolet`/`symbol-macrolet`), `mexpand`/`mexpand-all`.
|
||||
* [test.check](https://github.com/clojure/test.check) — property-based testing
|
||||
(generators, `quick-check`, shrinking).
|
||||
* [tick](https://github.com/juxt/tick) — date/time over Jolt's `java.time`;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -159,6 +159,15 @@
|
|||
(defmacro declare [& syms]
|
||||
`(do ~@(map (fn* [s] `(def ~s)) syms)))
|
||||
|
||||
;; letfn is a macro over the letfn* special form, matching Clojure: each
|
||||
;; (name [params] body*) spec becomes a name + a (fn name [params] body*) binding.
|
||||
;; So (macroexpand-1 '(letfn …)) yields the letfn* form macroexpansion tooling
|
||||
;; (tools.macro / tools.analyzer) expects, instead of an opaque special form.
|
||||
(defmacro letfn [fnspecs & body]
|
||||
(cons 'letfn*
|
||||
(cons (reduce (fn [acc s] (conj (conj acc (first s)) (cons 'fn s))) [] fnspecs)
|
||||
body)))
|
||||
|
||||
;; destructure — Clojure's binding-vector expander.
|
||||
;; Turns a binding vector that may contain destructuring
|
||||
;; patterns into a plain binding vector (alternating symbol / init-form) built from
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
;; analyzed in analyze-list), so keep them in sync by intent, not by equality.
|
||||
(def ^:private handled
|
||||
#{"quote" "if" "do" "def" "fn*" "let*" "loop*" "recur" "throw" "try"
|
||||
"syntax-quote" "var" "letfn" "set!" "defmacro"})
|
||||
"syntax-quote" "var" "letfn*" "set!" "defmacro"})
|
||||
|
||||
(defn- uncompilable [why]
|
||||
(throw (str "jolt/uncompilable: " why)))
|
||||
|
|
@ -286,26 +286,21 @@
|
|||
n)]
|
||||
n)))
|
||||
|
||||
;; letfn: (letfn [(name [params] body*)...] body*). The named local fns are
|
||||
;; MUTUALLY recursive, so bind every name into the env BEFORE analyzing any spec
|
||||
;; — each spec then resolves its siblings (and itself) as locals. Emitted as a
|
||||
;; :let flagged :letrec so the back end knows the bindings forward-reference each
|
||||
;; other: Chez lowers it to `letrec*`. The interpreter's shared mutable env already
|
||||
;; gives the letrec semantics that a
|
||||
;; compiled sequential let* lacks — the reason letfn was uncompilable before.
|
||||
(defn- analyze-letfn [ctx items env]
|
||||
(let [specs (vec (form-vec-items (nth items 1)))
|
||||
names (mapv #(form-sym-name (first (vec (form-elements %)))) specs)
|
||||
env* (add-locals env names)
|
||||
binds (mapv (fn [spec]
|
||||
(let [cl (vec (form-elements spec))]
|
||||
;; Build (fn name [params] body*) and analyze through the fn
|
||||
;; MACRO so destructuring params desugar (the fn* primitive
|
||||
;; would not — same trick defmacro uses). The named fn means
|
||||
;; self- and sibling-calls resolve and it carries its own name.
|
||||
[(form-sym-name (first cl))
|
||||
(analyze ctx (cons (symbol "fn") cl) env*)]))
|
||||
specs)]
|
||||
;; letfn*: (letfn* [name1 fn1 name2 fn2 …] body*) — the special form Clojure's
|
||||
;; letfn macro expands to (flat name/fn-form pairs, the fn forms already named).
|
||||
;; The named local fns are MUTUALLY recursive, so bind every name into the env
|
||||
;; BEFORE analyzing any fn form — each then resolves its siblings (and itself) as
|
||||
;; locals. Emitted as a :let flagged :letrec so the back end lowers it to
|
||||
;; `letrec*`; the interpreter's shared mutable env gives the same semantics.
|
||||
(defn- analyze-letfn* [ctx items env]
|
||||
(let [bvec (vec (form-vec-items (nth items 1)))
|
||||
n (quot (count bvec) 2)
|
||||
names (mapv (fn [i] (form-sym-name (nth bvec (* 2 i)))) (range n))
|
||||
env* (add-locals env names)
|
||||
binds (mapv (fn [i]
|
||||
[(nth names i)
|
||||
(analyze ctx (nth bvec (inc (* 2 i))) env*)])
|
||||
(range n))]
|
||||
{:op :let :letrec true :bindings binds
|
||||
:body (analyze-seq ctx (drop 2 items) env*)}))
|
||||
|
||||
|
|
@ -424,7 +419,7 @@
|
|||
{:op :recur :recur-name rt
|
||||
:args (mapv #(analyze ctx % env) (rest items))})
|
||||
"try" (analyze-try ctx items env)
|
||||
"letfn" (analyze-letfn ctx items env)
|
||||
"letfn*" (analyze-letfn* ctx items env)
|
||||
"fn*" (analyze-fn ctx items env)
|
||||
;; Lower the backtick to construction code (zero runtime cost), then analyze
|
||||
;; it — the macroexpand/compile-time step, per read -> macroexpand -> compile.
|
||||
|
|
|
|||
|
|
@ -3379,4 +3379,5 @@
|
|||
{:suite "numbers / ^long is 64-bit" :label "^long comparison on a full-width long" :expected "false" :actual "((fn* ([^long a ^long b] (< a b))) 9223372036854775807 1)"}
|
||||
{:suite "numbers / ^long is 64-bit" :label "^long quot on a full-width long" :expected "3074457345618258602" :actual "((fn* ([^long a] (quot a 3))) 9223372036854775807)"}
|
||||
{:suite "host-interop / Compiler" :label "Compiler/specials keys include the special forms" :expected "true" :actual "(every? (set (keys clojure.lang.Compiler/specials)) (quote [if do let* fn* quote def loop* recur try letfn* var]))"}
|
||||
{:suite "macros / letfn mutual recursion" :label "letfn binds mutually-recursive named fns" :expected "[true false]" :actual "(letfn [(ev? [n] (if (zero? n) true (od? (dec n)))) (od? [n] (if (zero? n) false (ev? (dec n))))] [(ev? 10) (ev? 7)])"}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue