core: Stage 3 turn 2a — close the implicit Janet root-env leak
resolve-sym's last resort silently resolved any unknown Clojure symbol against Janet's root environment — leaking Janet builtins with JANET semantics into Clojure code: (type 1) was Janet's :number, (gensym) returned Janet symbols (the long-documented (symbol (str (gensym))) macro landmine existed BECAUSE of this), compare/slurp/int?/any? likewise. The explicit janet/ prefix is the deliberate interop channel; the implicit fallback is gone — an unresolved symbol is an error. What the leak was masking, now proper interned vars: - gensym: jolt's own (already existed, never interned) — returns real jolt symbols; the macro landmine is dead - compare: full Clojure total order (nil-first, numbers, strings, keywords, symbols by ns/name, booleans, chars, uuid/inst, vectors by length then elementwise; cross-type throws) - type: :type metadata override, deftype/record tag as symbol, else a taxonomy keyword (host-classified) - int?: core-integer? — which had a latent bug the leak hid: (integer? ##Inf) was true (floor of inf is inf); NaN/infinities now excluded - any?: constantly true (Clojure 1.9; SCI's namespaces.cljc needs it) - jolt.interop/janet-type now uses the explicit (janet/type x) channel - evaluator-test uses init (a bare make-ctx resolved EVERYTHING via the leak) Suite 4470 -> 4532+ pass / 86-87 clean (proper compare unlocks the sort files); baselines raised. Conformance 326x3 (+5 rows), +22 predicate spec rows, stdlib battery green, all specs+unit. Coverage dashboard now counts previously-leak-resolvable names honestly (missing-portable 19 -> 27).
This commit is contained in:
parent
d0c605ac9d
commit
c7b0ad9d84
9 changed files with 150 additions and 37 deletions
|
|
@ -3,19 +3,19 @@
|
|||
Generated 2026-06-10 by `tools/spec_coverage.py` — do not edit by hand.
|
||||
|
||||
Surface: **694** clojure.core vars (ClojureDocs export; 648 with
|
||||
community examples). jolt interns 550 of them.
|
||||
community examples). jolt interns 535 of them.
|
||||
|
||||
| Status | Count | Meaning |
|
||||
|---|---|---|
|
||||
| implemented+tested | 399 | in jolt and exercised by spec/conformance |
|
||||
| implemented-untested | 151 | in jolt, no direct test — spec entries will add them |
|
||||
| resolvable-not-interned | 22 | works in code but invisible to ns introspection (conformance finding) |
|
||||
| missing-portable | 19 | portable semantics, jolt lacks it — implementation gap |
|
||||
| special-form | 13 | specified in §3, not a library var |
|
||||
| dynamic-var | 29 | classification needed: portable default vs host-dependent |
|
||||
| implemented+tested | 397 | in jolt and exercised by spec/conformance |
|
||||
| implemented-untested | 138 | in jolt, no direct test — spec entries will add them |
|
||||
| resolvable-not-interned | 2 | works in code but invisible to ns introspection (conformance finding) |
|
||||
| missing-portable | 27 | portable semantics, jolt lacks it — implementation gap |
|
||||
| special-form | 15 | specified in §3, not a library var |
|
||||
| dynamic-var | 31 | classification needed: portable default vs host-dependent |
|
||||
| agents-taps | 22 | out of scope pending concurrency design note |
|
||||
| stm-refs | 11 | out of scope pending concurrency design note |
|
||||
| jvm-specific | 50 | catalogued, not specified |
|
||||
| jvm-specific | 53 | catalogued, not specified |
|
||||
|
||||
Classifications are initial and mechanical — reclassifying is an ordinary
|
||||
spec change. A var is *Verified* only when its §9 entry exists and carries no
|
||||
|
|
@ -41,14 +41,14 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `*data-readers*` | dynamic-var | ✓ |
|
||||
| `*default-data-reader-fn*` | dynamic-var | ✓ |
|
||||
| `*e` | implemented-untested | ✓ |
|
||||
| `*err*` | implemented-untested | ✓ |
|
||||
| `*err*` | dynamic-var | ✓ |
|
||||
| `*file*` | dynamic-var | ✓ |
|
||||
| `*flush-on-newline*` | dynamic-var | |
|
||||
| `*fn-loader*` | dynamic-var | |
|
||||
| `*in*` | dynamic-var | |
|
||||
| `*math-context*` | dynamic-var | |
|
||||
| `*ns*` | dynamic-var | ✓ |
|
||||
| `*out*` | implemented-untested | ✓ |
|
||||
| `*out*` | dynamic-var | ✓ |
|
||||
| `*print-dup*` | dynamic-var | ✓ |
|
||||
| `*print-length*` | dynamic-var | ✓ |
|
||||
| `*print-level*` | dynamic-var | ✓ |
|
||||
|
|
@ -111,7 +111,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `amap` | jvm-specific | ✓ |
|
||||
| `ancestors` | implemented+tested | ✓ |
|
||||
| `and` | implemented+tested | ✓ |
|
||||
| `any?` | implemented-untested | ✓ |
|
||||
| `any?` | implemented+tested | ✓ |
|
||||
| `apply` | implemented+tested | ✓ |
|
||||
| `areduce` | jvm-specific | ✓ |
|
||||
| `array-map` | implemented-untested | ✓ |
|
||||
|
|
@ -193,7 +193,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `comparator` | implemented+tested | ✓ |
|
||||
| `compare` | implemented+tested | ✓ |
|
||||
| `compare-and-set!` | implemented+tested | ✓ |
|
||||
| `compile` | implemented-untested | ✓ |
|
||||
| `compile` | jvm-specific | ✓ |
|
||||
| `complement` | implemented+tested | ✓ |
|
||||
| `completing` | implemented+tested | ✓ |
|
||||
| `concat` | implemented+tested | ✓ |
|
||||
|
|
@ -221,7 +221,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `default-data-readers` | jvm-specific | ✓ |
|
||||
| `definline` | jvm-specific | |
|
||||
| `definterface` | implemented-untested | ✓ |
|
||||
| `defmacro` | implemented+tested | ✓ |
|
||||
| `defmacro` | special-form | ✓ |
|
||||
| `defmethod` | implemented+tested | ✓ |
|
||||
| `defmulti` | implemented+tested | ✓ |
|
||||
| `defn` | implemented+tested | ✓ |
|
||||
|
|
@ -267,7 +267,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `enumeration-seq` | implemented-untested | ✓ |
|
||||
| `error-handler` | agents-taps | ✓ |
|
||||
| `error-mode` | agents-taps | ✓ |
|
||||
| `eval` | implemented+tested | ✓ |
|
||||
| `eval` | missing-portable | ✓ |
|
||||
| `even?` | implemented+tested | ✓ |
|
||||
| `every-pred` | implemented+tested | ✓ |
|
||||
| `every?` | implemented+tested | ✓ |
|
||||
|
|
@ -290,7 +290,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `find-keyword` | missing-portable | ✓ |
|
||||
| `find-ns` | implemented+tested | ✓ |
|
||||
| `find-protocol-impl` | jvm-specific | |
|
||||
| `find-protocol-method` | implemented-untested | |
|
||||
| `find-protocol-method` | jvm-specific | |
|
||||
| `find-var` | implemented+tested | ✓ |
|
||||
| `first` | implemented+tested | ✓ |
|
||||
| `flatten` | implemented+tested | ✓ |
|
||||
|
|
@ -298,7 +298,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `float-array` | implemented-untested | ✓ |
|
||||
| `float?` | implemented+tested | ✓ |
|
||||
| `floats` | implemented-untested | ✓ |
|
||||
| `flush` | implemented-untested | ✓ |
|
||||
| `flush` | missing-portable | ✓ |
|
||||
| `fn` | implemented+tested | ✓ |
|
||||
| `fn?` | implemented+tested | ✓ |
|
||||
| `fnext` | implemented+tested | ✓ |
|
||||
|
|
@ -383,7 +383,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `load` | jvm-specific | ✓ |
|
||||
| `load-file` | jvm-specific | ✓ |
|
||||
| `load-reader` | jvm-specific | ✓ |
|
||||
| `load-string` | implemented-untested | ✓ |
|
||||
| `load-string` | missing-portable | ✓ |
|
||||
| `loaded-libs` | jvm-specific | ✓ |
|
||||
| `locking` | implemented+tested | ✓ |
|
||||
| `long` | implemented-untested | ✓ |
|
||||
|
|
@ -437,13 +437,13 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `ns-aliases` | implemented+tested | ✓ |
|
||||
| `ns-imports` | implemented-untested | ✓ |
|
||||
| `ns-interns` | implemented-untested | ✓ |
|
||||
| `ns-map` | implemented-untested | ✓ |
|
||||
| `ns-map` | missing-portable | ✓ |
|
||||
| `ns-name` | implemented+tested | ✓ |
|
||||
| `ns-publics` | implemented+tested | ✓ |
|
||||
| `ns-refers` | missing-portable | ✓ |
|
||||
| `ns-resolve` | implemented+tested | ✓ |
|
||||
| `ns-unalias` | implemented+tested | ✓ |
|
||||
| `ns-unmap` | implemented-untested | ✓ |
|
||||
| `ns-unmap` | missing-portable | ✓ |
|
||||
| `nth` | implemented+tested | ✓ |
|
||||
| `nthnext` | implemented+tested | ✓ |
|
||||
| `nthrest` | implemented+tested | ✓ |
|
||||
|
|
@ -484,7 +484,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `print-method` | implemented-untested | ✓ |
|
||||
| `print-simple` | jvm-specific | ✓ |
|
||||
| `print-str` | implemented+tested | ✓ |
|
||||
| `printf` | implemented+tested | ✓ |
|
||||
| `printf` | missing-portable | ✓ |
|
||||
| `println` | implemented+tested | ✓ |
|
||||
| `println-str` | implemented+tested | ✓ |
|
||||
| `prn` | implemented+tested | ✓ |
|
||||
|
|
@ -594,7 +594,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `simple-ident?` | implemented+tested | ✓ |
|
||||
| `simple-keyword?` | implemented+tested | ✓ |
|
||||
| `simple-symbol?` | implemented+tested | ✓ |
|
||||
| `slurp` | implemented-untested | ✓ |
|
||||
| `slurp` | missing-portable | ✓ |
|
||||
| `some` | implemented+tested | ✓ |
|
||||
| `some->` | implemented+tested | ✓ |
|
||||
| `some->>` | implemented+tested | ✓ |
|
||||
|
|
@ -608,7 +608,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `sorted-set-by` | implemented+tested | ✓ |
|
||||
| `sorted?` | implemented+tested | ✓ |
|
||||
| `special-symbol?` | implemented-untested | ✓ |
|
||||
| `spit` | implemented-untested | ✓ |
|
||||
| `spit` | missing-portable | ✓ |
|
||||
| `split-at` | implemented+tested | ✓ |
|
||||
| `split-with` | implemented+tested | ✓ |
|
||||
| `splitv-at` | implemented+tested | |
|
||||
|
|
@ -618,7 +618,7 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `stream-seq!` | jvm-specific | |
|
||||
| `stream-transduce!` | jvm-specific | |
|
||||
| `string?` | implemented+tested | ✓ |
|
||||
| `struct` | implemented-untested | ✓ |
|
||||
| `struct` | jvm-specific | ✓ |
|
||||
| `struct-map` | jvm-specific | ✓ |
|
||||
| `subs` | implemented+tested | ✓ |
|
||||
| `subseq` | implemented+tested | ✓ |
|
||||
|
|
@ -648,8 +648,8 @@ UNVERIFIED field; that column will be added as entries land.
|
|||
| `transient` | implemented+tested | ✓ |
|
||||
| `tree-seq` | implemented+tested | ✓ |
|
||||
| `true?` | implemented+tested | ✓ |
|
||||
| `try` | implemented+tested | ✓ |
|
||||
| `type` | implemented-untested | ✓ |
|
||||
| `try` | special-form | ✓ |
|
||||
| `type` | implemented+tested | ✓ |
|
||||
| `unchecked-add` | implemented-untested | ✓ |
|
||||
| `unchecked-add-int` | implemented-untested | ✓ |
|
||||
| `unchecked-byte` | implemented-untested | ✓ |
|
||||
|
|
|
|||
|
|
@ -148,6 +148,9 @@
|
|||
|
||||
(defn keyword-identical? [a b] (= a b))
|
||||
|
||||
;; Clojure 1.9: true for ANY argument incl. nil (used as a spec predicate).
|
||||
(defn any? [x] true)
|
||||
|
||||
(defn comparator [pred]
|
||||
(fn [a b] (cond (pred a b) -1 (pred b a) 1 :else 0)))
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,12 @@
|
|||
(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))))
|
||||
# Finite integral number: NaN and the infinities are NOT integers (floor of
|
||||
# inf is inf, so the naive floor check wrongly accepted them).
|
||||
(defn core-integer? [x]
|
||||
(and (number? x) (= x x)
|
||||
(< x math/inf) (> x (- math/inf))
|
||||
(= 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)))))
|
||||
|
||||
|
|
@ -1827,6 +1832,74 @@
|
|||
# Host time source for the `time` macro (monotonic, milliseconds).
|
||||
(defn core-current-time-ms [] (* 1000 (os/clock :monotonic)))
|
||||
|
||||
# Clojure compare: a total order over comparable values. nil sorts first;
|
||||
# numbers numerically; strings/keywords lexically; symbols by ns then name;
|
||||
# booleans false<true; chars by codepoint; vectors by length then elementwise;
|
||||
# uuids by canonical string; insts by epoch ms. Cross-type comparison throws
|
||||
# (like Clojure's ClassCastException).
|
||||
(var core-compare nil)
|
||||
(set core-compare (fn ccompare [a b]
|
||||
(defn cmp3 [x y] (cond (< x y) -1 (> x y) 1 0))
|
||||
(cond
|
||||
(and (nil? a) (nil? b)) 0
|
||||
(nil? a) -1
|
||||
(nil? b) 1
|
||||
(and (number? a) (number? b)) (cmp3 a b)
|
||||
(and (or (string? a) (buffer? a)) (or (string? b) (buffer? b)))
|
||||
(cmp3 (string a) (string b))
|
||||
(and (keyword? a) (keyword? b)) (cmp3 (string a) (string b))
|
||||
(and (core-symbol? a) (core-symbol? b))
|
||||
(let [r (cmp3 (string (or (a :ns) "")) (string (or (b :ns) "")))]
|
||||
(if (= 0 r) (cmp3 (a :name) (b :name)) r))
|
||||
(and (boolean? a) (boolean? b))
|
||||
(cond (= a b) 0 (= a false) -1 1)
|
||||
(and (core-char? a) (core-char? b)) (cmp3 (a :ch) (b :ch))
|
||||
(and (struct? a) (= :jolt/uuid (get a :jolt/type))
|
||||
(struct? b) (= :jolt/uuid (get b :jolt/type)))
|
||||
(cmp3 (a :str) (b :str))
|
||||
(and (struct? a) (= :jolt/inst (get a :jolt/type))
|
||||
(struct? b) (= :jolt/inst (get b :jolt/type)))
|
||||
(cmp3 (a :ms) (b :ms))
|
||||
(and (jvec? a) (jvec? b))
|
||||
(let [la (vcount a) lb (vcount b)]
|
||||
(if (not= la lb)
|
||||
(cmp3 la lb)
|
||||
(do
|
||||
(var r 0) (var i 0)
|
||||
(while (and (= r 0) (< i la))
|
||||
(set r (ccompare (vnth a i) (vnth b i)))
|
||||
(++ i))
|
||||
r)))
|
||||
(error (string "Cannot compare " (type a) " with " (type b))))))
|
||||
|
||||
# Clojure type: the :type metadata when present, else the value's type. With no
|
||||
# class objects on this host, the "class" is a symbol: a deftype/record value
|
||||
# yields its type tag symbol; everything else a taxonomy keyword
|
||||
# (host-classified — see spec coverage).
|
||||
(defn core-type [x]
|
||||
(def m (core-meta x))
|
||||
(def override (and m (core-get m :type)))
|
||||
(if (not (nil? override))
|
||||
override
|
||||
(cond
|
||||
(and (table? x) (get x :jolt/deftype))
|
||||
{:jolt/type :symbol :ns nil :name (get x :jolt/deftype)}
|
||||
(nil? x) nil
|
||||
(boolean? x) :boolean
|
||||
(number? x) :number
|
||||
(or (string? x) (buffer? x)) :string
|
||||
(keyword? x) :keyword
|
||||
(core-symbol? x) :symbol
|
||||
(core-char? x) :char
|
||||
(and (struct? x) (get x :jolt/type)) (get x :jolt/type)
|
||||
(jvec? x) :vector
|
||||
(core-map? x) :map
|
||||
(set? x) :set
|
||||
(core-seq? x) :seq
|
||||
(or (function? x) (cfunction? x)) :fn
|
||||
(table? x) (or (get x :jolt/type) :table)
|
||||
:else (keyword (type x)))))
|
||||
|
||||
# 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.
|
||||
|
|
@ -2994,6 +3067,10 @@
|
|||
"hash-unordered-coll" core-hash-unordered-coll
|
||||
"prefers" core-prefers
|
||||
"random-uuid" core-random-uuid
|
||||
"gensym" gensym
|
||||
"int?" core-integer?
|
||||
"compare" core-compare
|
||||
"type" core-type
|
||||
"parse-long" core-parse-long
|
||||
"parse-double" core-parse-double
|
||||
"parse-boolean" core-parse-boolean
|
||||
|
|
|
|||
|
|
@ -470,12 +470,10 @@
|
|||
class-name (string/slice name (+ last-dot 1))]
|
||||
(let [target-ns (ctx-find-ns ctx class-ns) tv (ns-find target-ns class-name)]
|
||||
(if tv (var-get tv) tv)))
|
||||
# Fall back to Janet's global environment
|
||||
(let [root-env (fiber/getenv (fiber/current))
|
||||
entry (in root-env (symbol name))]
|
||||
(if (not (nil? entry))
|
||||
(if (table? entry) (entry :value) entry)
|
||||
(error (string "Unable to resolve symbol: " name))))))))))))))))
|
||||
# No implicit Janet fallback (Stage 3): an unresolved
|
||||
# Clojure symbol is an error. Host access is the explicit
|
||||
# janet/ prefix above.
|
||||
(error (string "Unable to resolve symbol: " name))))))))))))))
|
||||
|
||||
(defn- parse-arg-names
|
||||
"Parse a parameter vector, handling & rest args.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
(defn janet-type
|
||||
[x]
|
||||
(type x))
|
||||
(janet/type x))
|
||||
|
||||
(defn janet-describe
|
||||
[x]
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
# Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the
|
||||
# overlay, which required fixing two leniencies (a char is not callable; take
|
||||
# validates its count) — correct beyond those fns, so the suite rose broadly.
|
||||
(def baseline-pass 4470)
|
||||
(def baseline-pass 4532)
|
||||
# A file is "clean" when it ran with zero failures AND zero errors.
|
||||
(def baseline-clean-files 86)
|
||||
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so
|
||||
|
|
|
|||
|
|
@ -200,6 +200,13 @@
|
|||
["inst offset normalized" "true" "(= #inst \"2020-01-01T01:00:00+01:00\" #inst \"2020-01-01T00:00:00Z\")"]
|
||||
["sq literal collapse" "true" "(= \"meow\" ```\"meow\")"]
|
||||
["sq number collapse" "42" "``42"]
|
||||
|
||||
### ---- stage 3: proper vars replace the Janet root-env leak ----
|
||||
["compare total order" "[-1 0 1]" "[(compare nil 1) (compare :a :a) (compare \"b\" \"a\")]"]
|
||||
["compare vectors" "-1" "(compare [1 2] [1 3])"]
|
||||
["gensym jolt symbol" "true" "(symbol? (gensym))"]
|
||||
["any? anything" "true" "(and (any? nil) (any? 1) (any? :k))"]
|
||||
["int? excludes Inf" "false" "(int? ##Inf)"]
|
||||
["macroexpand-1 when" "2" "(count (rest (macroexpand-1 (quote (when true 1)))))"]
|
||||
|
||||
### ---- HIGH: aliased namespace calls ----
|
||||
|
|
|
|||
|
|
@ -157,3 +157,30 @@
|
|||
["not-empty full" "[1]" "(not-empty [1])"]
|
||||
["not-empty empty" "nil" "(not-empty [])"]
|
||||
["not-empty string" "nil" "(not-empty \"\")"])
|
||||
|
||||
# Stage 3 turn 2a: the implicit Janet root-env fallback is GONE — these are now
|
||||
# proper interned clojure.core vars with Clojure semantics (compare's total
|
||||
# order, meta-aware type, any?, gensym returning jolt symbols).
|
||||
(defspec "predicates / compare, type, any? (stage 3)"
|
||||
["compare =" "0" "(compare 1 1)"]
|
||||
["compare <" "-1" "(compare 1 2)"]
|
||||
["compare nil first" "-1" "(compare nil 1)"]
|
||||
["compare nil nil" "0" "(compare nil nil)"]
|
||||
["compare strings" "-1" "(compare \"a\" \"b\")"]
|
||||
["compare keywords" "-1" "(compare :a :b)"]
|
||||
["compare symbols" "-1" "(compare (quote a) (quote b))"]
|
||||
["compare bools" "-1" "(compare false true)"]
|
||||
["compare vec length" "-1" "(compare [1 2] [1 2 3])"]
|
||||
["compare vec elems" "-1" "(compare [1 2] [1 3])"]
|
||||
["compare cross-type throws" :throws "(compare 1 \"a\")"]
|
||||
["sort with compare" "[nil 1 3]" "(sort compare [3 nil 1])"]
|
||||
["type meta override" ":custom" "(type (with-meta [1] {:type :custom}))"]
|
||||
["type of record" "true" "(do (defrecord TyR [a]) (= (symbol (str (type (->TyR 1)))) (type (->TyR 1))))"]
|
||||
["any? value" "true" "(any? 5)"]
|
||||
["any? nil" "true" "(any? nil)"]
|
||||
["gensym is symbol" "true" "(symbol? (gensym))"]
|
||||
["gensym prefix" "true" "(do (require (quote [clojure.string :as s])) (s/starts-with? (str (gensym \"p_\")) \"p_\"))"]
|
||||
["gensym distinct" "false" "(= (gensym) (gensym))"]
|
||||
["int? Inf false" "false" "(int? ##Inf)"]
|
||||
["integer? Inf false" "false" "(integer? ##Inf)"]
|
||||
["integer? NaN false" "false" "(integer? ##NaN)"])
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@
|
|||
{:jolt/type :symbol :ns nil :name name})))
|
||||
|
||||
# Helper: parse and eval
|
||||
# init, not bare make-ctx: with the implicit Janet root-env fallback removed
|
||||
# (Stage 3), a context without clojure.core interned can't resolve inc/+/etc.
|
||||
(def- shared-ctx (init))
|
||||
(defn eval-str [s]
|
||||
(let [ctx (make-ctx)
|
||||
form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
(eval-form shared-ctx @{} (parse-string s)))
|
||||
|
||||
(print "1: literals...")
|
||||
(assert (= 42 (eval-str "42")) "integer")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue