core: spec 35-var batch A — 1.11 parsers, map/partition variants, with-redefs, ns fns
Fifteen vars from the spec coverage gap (docs/spec/coverage.md): parse-long/parse-double/parse-boolean (strict validation; scan-number alone accepts 0x10), newline, current-time-ms (host clock for time), update-keys/ update-vals (PHM base, collisions last-wins), partitionv/partitionv-all/ splitv-at (lazy seqs of vectors; splitv-at's tail stays a seq, matching the reference), with-redefs/with-redefs-fn (roots restored on throw), time, macroexpand (expand-1 to fixpoint), alias/ns-unalias (write BOTH alias stores — require :as uses string-keyed :imports while ns-aliases reads :aliases; split filed), ns-publics (symbol-keyed map; publics == interns, no privacy). Three pre-existing bugs fixed along the way: - (partition n step pad coll) misparsed pad as the coll and returned () - (require 'bare.symbol) rejected — only vector specs were accepted - analyze-form leaked the interpreted analyzer's ns on a punt: a throw out of the analyzer left current-ns=jolt.analyzer and :compile-ns set, so the fallback interpretation resolved user vars against the wrong namespace (bit (var user-sym) under compile mode) And one overlay-authoring landmine documented in-code: a 20-coll fn must not use 30-macros macros (with-redefs-fn's dotimes compiled as a forward ref that resolved to the macro fn at runtime) — loop/recur instead. Gate: conformance 316x3 (+14 rows), suite 4324 pass / 78 clean (was 4081/72; parse_*/update_* files now contribute), baselines raised, all specs+unit, fixpoint, self-host, sci, staged. Coverage: missing-portable 35 -> 20.
This commit is contained in:
parent
894af34b4c
commit
eb7a9f1b20
13 changed files with 277 additions and 44 deletions
|
|
@ -351,15 +351,24 @@
|
|||
# jolt.analyzer), and the interpreter rebinds current-ns to a fn's defining ns
|
||||
# while it runs — so h/current-ns must read this instead of ctx-current-ns.
|
||||
(put (ctx :env) :compile-ns (ctx-current-ns ctx))
|
||||
(def saved-ns (ctx-current-ns ctx))
|
||||
(def av (ns-find (ctx-find-ns ctx "jolt.analyzer") "analyze"))
|
||||
# Pre-kernel bootstrap: ensure-analyzer is gated until the kernel tier loads
|
||||
# (see api/load-core-overlay!), so a compile request from an earlier tier (e.g.
|
||||
# 00-syntax's destructure defn) finds no analyzer. That fallback is DESIGNED —
|
||||
# route it through the sanctioned punt channel rather than crashing on a nil var.
|
||||
(unless av (error "jolt/uncompilable: analyzer not built (pre-kernel bootstrap)"))
|
||||
(def r ((var-get av) ctx form))
|
||||
(unless av
|
||||
(put (ctx :env) :compile-ns nil)
|
||||
(error "jolt/uncompilable: analyzer not built (pre-kernel bootstrap)"))
|
||||
# The analyzer runs INTERPRETED; the interpreter rebinds current-ns to a fn's
|
||||
# defining ns (jolt.analyzer) while it runs and only restores on normal return.
|
||||
# A punt THROWS out of those frames, leaking jolt.analyzer as current-ns (and
|
||||
# :compile-ns stayed set) — the fallback interpretation then resolves user vars
|
||||
# against the wrong ns. Restore both on every exit.
|
||||
(def r (protect ((var-get av) ctx form)))
|
||||
(put (ctx :env) :compile-ns nil)
|
||||
r)
|
||||
(ctx-set-current-ns ctx saved-ns)
|
||||
(if (r 0) (r 1) (error (r 1))))
|
||||
|
||||
# The analyzer's deliberate punt signal — (uncompilable why) throws the string
|
||||
# "jolt/uncompilable: <why>". Anything else escaping the compile step is an
|
||||
|
|
|
|||
|
|
@ -1409,12 +1409,15 @@
|
|||
# (core/20-coll.clj).
|
||||
|
||||
(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)."
|
||||
"(partition n coll), (partition n step coll), or (partition n step pad coll).
|
||||
Only complete partitions of size n are kept; with pad, the final partial
|
||||
partition is padded from pad (possibly to fewer than n if pad runs out)."
|
||||
[n & rest]
|
||||
(let [has-step (> (length rest) 1)
|
||||
step (if has-step (first rest) n)
|
||||
coll (if has-step (in rest 1) (first rest))]
|
||||
(let [argc (length rest)
|
||||
step (if (>= argc 2) (first rest) n)
|
||||
pad (if (>= argc 3) (in rest 1) nil)
|
||||
has-pad (>= argc 3)
|
||||
coll (case argc 1 (first rest) 2 (in rest 1) 3 (in rest 2))]
|
||||
# Option A: always lazy.
|
||||
(defn pstep [c]
|
||||
(fn []
|
||||
|
|
@ -1425,9 +1428,16 @@
|
|||
(array/push part (core-first cur))
|
||||
(set cur (core-rest cur))
|
||||
(++ i))
|
||||
(if (= i n)
|
||||
(cond
|
||||
(= i n)
|
||||
(let [next-cur (if (= step n) cur (lazy-from (core-drop (- step n) cur)))]
|
||||
@[(tuple/slice (tuple ;part)) (pstep next-cur)])
|
||||
# partial final partition: pad it (last partition, then stop)
|
||||
(and has-pad (> i 0))
|
||||
(do
|
||||
(each x (realize-for-iteration pad)
|
||||
(when (< (length part) n) (array/push part x)))
|
||||
@[(tuple/slice (tuple ;part)) (fn [] nil)])
|
||||
nil)))))
|
||||
(make-lazy-seq (pstep (lazy-from coll)))))
|
||||
|
||||
|
|
@ -1775,6 +1785,44 @@
|
|||
(prin "\n")
|
||||
nil)
|
||||
|
||||
(defn core-newline [] (prin "\n") nil)
|
||||
|
||||
# Clojure 1.11 string->scalar parsers: nil on malformed input, throw on a
|
||||
# non-string. Validation is strict (scan-number alone accepts 0x10 etc.).
|
||||
(defn- parse-arg-str [s who]
|
||||
(if (or (string? s) (buffer? s)) (string s)
|
||||
(error (string who " requires a string, got " (type s)))))
|
||||
|
||||
(defn core-parse-long [s]
|
||||
(def str* (parse-arg-str s "parse-long"))
|
||||
(def n (length str*))
|
||||
(def start (if (and (> n 0) (or (= 43 (in str* 0)) (= 45 (in str* 0)))) 1 0))
|
||||
(if (and (> n start)
|
||||
(do (var ok true)
|
||||
(for i start n (when (or (< (in str* i) 48) (> (in str* i) 57)) (set ok false)))
|
||||
ok))
|
||||
(scan-number str*)
|
||||
nil))
|
||||
|
||||
(defn core-parse-double [s]
|
||||
(def str* (parse-arg-str s "parse-double"))
|
||||
# strict float shape: [+-] digits [. digits] [eE [+-] digits] — at least one
|
||||
# digit overall; "Infinity"/"-Infinity"/"NaN" accepted like the reference.
|
||||
(cond
|
||||
(= str* "Infinity") math/inf
|
||||
(= str* "-Infinity") (- math/inf)
|
||||
(= str* "NaN") math/nan
|
||||
(do
|
||||
(def pat (peg/compile ~(sequence (opt (set "+-")) (choice (sequence (some :d) (opt (sequence "." (any :d)))) (sequence "." (some :d))) (opt (sequence (set "eE") (opt (set "+-")) (some :d))) -1)))
|
||||
(if (peg/match pat str*) (scan-number str*) nil))))
|
||||
|
||||
(defn core-parse-boolean [s]
|
||||
(def str* (parse-arg-str s "parse-boolean"))
|
||||
(case str* "true" true "false" false nil))
|
||||
|
||||
# Host time source for the `time` macro (monotonic, milliseconds).
|
||||
(defn core-current-time-ms [] (* 1000 (os/clock :monotonic)))
|
||||
|
||||
# Capture *out*: run thunk with Janet's :out dynamic bound to a buffer, so all
|
||||
# print/println/pr/prn output (which go through `prin` -> (dyn :out)) is collected
|
||||
# and returned as a string. The with-out-str macro (overlay) wraps a body thunk.
|
||||
|
|
@ -2942,6 +2990,11 @@
|
|||
"hash-unordered-coll" core-hash-unordered-coll
|
||||
"prefers" core-prefers
|
||||
"random-uuid" core-random-uuid
|
||||
"parse-long" core-parse-long
|
||||
"parse-double" core-parse-double
|
||||
"parse-boolean" core-parse-boolean
|
||||
"newline" core-newline
|
||||
"current-time-ms" core-current-time-ms
|
||||
"parse-uuid" core-parse-uuid
|
||||
"interpose" core-interpose
|
||||
"mapcat" core-mapcat
|
||||
|
|
|
|||
|
|
@ -784,13 +784,14 @@
|
|||
(defn require-impl
|
||||
"(require '[ns :as a :refer [...]] ...) — load + alias/refer each spec. A fn, so
|
||||
the args (quoted specs) arrive evaluated. Varargs (Clojure-compatible); each spec
|
||||
is a vector. eval-require does the namespace load + alias/refer into current-ns."
|
||||
is a vector [ns & opts] or a bare ns symbol (treated as [ns])."
|
||||
[ctx & specs]
|
||||
(each spec specs
|
||||
(let [s (if (pvec? spec) (pv->array spec) spec)]
|
||||
(if (and (indexed? s) (> (length s) 0))
|
||||
(eval-require ctx s)
|
||||
(error "require expects a vector spec"))))
|
||||
(cond
|
||||
(and (indexed? s) (> (length s) 0)) (eval-require ctx s)
|
||||
(and (struct? s) (= :symbol (s :jolt/type))) (eval-require ctx @[s])
|
||||
(error "require expects a vector spec or a namespace symbol"))))
|
||||
nil)
|
||||
|
||||
(defn in-ns-impl
|
||||
|
|
@ -1102,15 +1103,47 @@
|
|||
# Reader / expansion as plain fns: read-string parses one form; macroexpand-1
|
||||
# expands a (quoted, already-evaluated) call form once via its macro var.
|
||||
(ns-intern core "read-string" (fn [s] (parse-string s)))
|
||||
(ns-intern core "macroexpand-1"
|
||||
(def expand-1 (fn [the-form]
|
||||
(if (and (array? the-form) (> (length the-form) 0)
|
||||
(struct? (first the-form)) (= :symbol ((first the-form) :jolt/type)))
|
||||
(let [v (resolve-var ctx @{} (first the-form))]
|
||||
(if (and v (var-macro? v))
|
||||
(apply (var-get v) (tuple/slice the-form 1))
|
||||
the-form))
|
||||
the-form)))
|
||||
(ns-intern core "macroexpand-1" expand-1)
|
||||
# macroexpand: expand repeatedly until the head is no longer a macro (the
|
||||
# form's SUBFORMS are not expanded, matching Clojure).
|
||||
(ns-intern core "macroexpand"
|
||||
(fn [the-form]
|
||||
(if (and (array? the-form) (> (length the-form) 0)
|
||||
(struct? (first the-form)) (= :symbol ((first the-form) :jolt/type)))
|
||||
(let [v (resolve-var ctx @{} (first the-form))]
|
||||
(if (and v (var-macro? v))
|
||||
(apply (var-get v) (tuple/slice the-form 1))
|
||||
the-form))
|
||||
the-form)))
|
||||
(var cur the-form)
|
||||
(var nxt (expand-1 cur))
|
||||
(while (not= cur nxt) (set cur nxt) (set nxt (expand-1 cur)))
|
||||
cur))
|
||||
# alias/ns-unalias: alias bookkeeping is currently split (require :as writes
|
||||
# the string-keyed :imports table that resolution reads; :aliases is the
|
||||
# introspection table ns-aliases reads) — write/remove BOTH until unified.
|
||||
(ns-intern core "alias"
|
||||
(fn [alias-sym ns-sym]
|
||||
(def cur (ctx-find-ns ctx (ctx-current-ns ctx)))
|
||||
(ns-import cur (alias-sym :name) (ns-sym :name))
|
||||
(ns-add-alias cur alias-sym (ctx-find-ns ctx (ns-sym :name)))
|
||||
nil))
|
||||
(ns-intern core "ns-unalias"
|
||||
(fn [ns-d alias-sym]
|
||||
(def ns (ns-or-current ns-d))
|
||||
(put (ns :imports) (alias-sym :name) nil)
|
||||
(put (ns :aliases) alias-sym nil)
|
||||
nil))
|
||||
# ns-publics: {symbol -> var} (jolt has no private vars, so publics = interns).
|
||||
# Keys are symbol structs (value-hashed), matching Clojure's symbol keys.
|
||||
(ns-intern core "ns-publics"
|
||||
(fn [&opt ns-d]
|
||||
(def ns (ns-or-current ns-d))
|
||||
(var m (make-phm))
|
||||
(loop [[nm v] :pairs (ns :mappings)]
|
||||
(set m (phm-assoc m {:jolt/type :symbol :ns nil :name nm} v)))
|
||||
m))
|
||||
core)
|
||||
|
||||
# Dispatch a special form by its string name.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue