From a6f0f8a6fe5e03ed1d44550efc0f1a6b3f0287c5 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 12:08:00 -0400 Subject: [PATCH] core: move last/butlast to the Clojure overlay Continue the kernel shrink. last/butlast become canonical Clojure defs (pure first/next/loop/recur) in the core.clj overlay, dropping two realize-for-iteration uses from the Janet kernel. second was the obvious next candidate but can't move: the self-hosted compiler front-end (analyzer.clj) calls it, and the compiler has to compile the overlay, so anything it uses must already exist as a Janet primitive. Documented that as a third clause of the safe-to-move rule. Suite green, clojure-test-suite battery holds 3913 pass / 58 clean, binary builds and runs the embedded overlay. --- jolt-core/clojure/core.clj | 18 ++++++++++++++++++ src/jolt/core.janet | 15 ++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/jolt-core/clojure/core.clj b/jolt-core/clojure/core.clj index 72bb0a9..4d9f4f3 100644 --- a/jolt-core/clojure/core.clj +++ b/jolt-core/clojure/core.clj @@ -6,8 +6,26 @@ ;; of the remaining Janet primitives move here from core.janet, one at a time, ;; each compiled by the prior stage. Anything here must depend only on core vars ;; already interned by init-core! (and on other overlay fns defined above it). +;; +;; Safe-to-move rule: a fn can move here only if it is (1) NOT in +;; compiler/core-renames (that map emits core-X Janet symbols directly), (2) has +;; no internal Janet callers of its core-X binding, and (3) is NOT used by the +;; self-hosted compiler itself (jolt-core/jolt/*.clj) — the compiler has to +;; compile this overlay, so anything it calls must already exist as a Janet +;; primitive. (That last rule is why `second`, used by analyzer.clj, stays in +;; Janet even though it has no Janet callers.) (defn ffirst [coll] (first (first coll))) (defn nfirst [coll] (next (first coll))) (defn fnext [coll] (first (next coll))) (defn nnext [coll] (next (next coll))) + +;; Canonical Clojure defs: pure first/next/loop/recur, no Janet realize-for-iteration. +(defn last [s] + (if (next s) (recur (next s)) (first s))) + +(defn butlast [s] + (loop [ret [] s s] + (if (next s) + (recur (conj ret (first s)) (next s)) + (seq ret)))) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index f0d4add..cce1ffd 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -940,11 +940,10 @@ (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))))) +# ffirst / nfirst / fnext / nnext / last / butlast now live in the Clojure overlay +# (jolt-core/clojure/core.clj). second stays here — the self-hosted compiler +# (analyzer.clj) calls it, so it must exist as a Janet primitive before the +# overlay (which the compiler compiles) loads. (defn core-drop-last [a & rest] (let [n (if (= 0 (length rest)) 1 a) @@ -3098,10 +3097,6 @@ (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))) @@ -3806,7 +3801,6 @@ "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 @@ -3834,7 +3828,6 @@ "take-nth" core-take-nth "nthrest" core-nthrest "nthnext" core-nthnext - "butlast" core-butlast "filterv" core-filterv "mapv" core-mapv "empty" core-empty