From 1bd676c2429bcf4f1142c2293756da0df9aa3d8c Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 19:45:30 -0400 Subject: [PATCH] edn: port read-string onto the reader; fix special-form ns shadowing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clojure.edn/read-string now delegates to clojure.core/read-string (the reader-based special form — parses, never evals) via a private helper, with the opts-map :eof arity and nil/blank-input handling. 43 -> 47 in the stdlib battery. This required an evaluator fix: special forms were matched by name only, so clojure.edn/read-string dispatched the core read-string SPECIAL FORM instead of the var. Now a head qualified to a non-core namespace resolves to its var; only unqualified or clojure.core-qualified heads are special forms. Conformance 218/218 all modes; full suite green. Remaining edn gaps (jolt-b7y): set literals read as a set FORM not a constructed set, and #uuid has no real type. --- src/jolt/clojure/edn.clj | 28 ++++++++++++++----- src/jolt/evaluator.janet | 8 ++++-- .../clojure-stdlib-suite-test.janet | 7 +++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/jolt/clojure/edn.clj b/src/jolt/clojure/edn.clj index 505765e..6de5146 100644 --- a/src/jolt/clojure/edn.clj +++ b/src/jolt/clojure/edn.clj @@ -1,13 +1,27 @@ -; Jolt Standard Library: clojure.edn -; EDN reading and writing (stubs using the Jolt reader). +;; clojure.edn — reading EDN data. Delegates to the Jolt reader via +;; clojure.core/read-string (which parses, never evaluates — safe for EDN), and +;; adds the opts-map arity with :eof plus nil/blank-input handling. +(ns clojure.edn + "Reading EDN data." + (:require [clojure.string :as cstr])) + +;; Private helper, NOT named read-string: an unqualified (read-string …) call +;; dispatches the core read-string SPECIAL FORM (by name, regardless of ns), so +;; the 1-arity can't delegate to the 2-arity through that name. +(defn- read-edn [opts s] + (if (or (nil? s) (cstr/blank? s)) + (get opts :eof nil) + (clojure.core/read-string s))) (defn read-string - [s] - (let [ctx ((get (dyn :current-env) (symbol "init")))] - ((get (dyn :current-env) (symbol "eval-string")) ctx s))) + "Reads one object from the string s. Returns the :eof option value (default + nil) for nil or blank input. opts is an options map; :eof sets the value + returned at end of input." + ([s] (read-edn {} s)) + ([opts s] (read-edn opts s))) (defn read + "Reads the next line from reader and parses one EDN object from it." [reader] (let [line ((get (dyn :current-env) (symbol "file/read")) reader :line)] - (when line - (read-string line)))) + (when line (read-string line)))) diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index f64fe89..fdd90a6 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -611,9 +611,13 @@ (defn- eval-list [ctx bindings form] (def first-form (first form)) - # Safe name extraction: non-symbol heads (e.g. keywords) fall through to default + # Safe name extraction: non-symbol heads (e.g. keywords) fall through to default. + # A head qualified to a NON-core namespace (e.g. clojure.edn/read-string) must + # resolve to that var, not the like-named clojure.core special form — so only + # unqualified or clojure.core-qualified heads dispatch as special forms. (def name (if (and (struct? first-form) (= :symbol (first-form :jolt/type))) - (first-form :name) + (let [ns (first-form :ns)] + (if (or (nil? ns) (= ns "clojure.core")) (first-form :name) nil)) nil)) (match name "quote" (in form 1) diff --git a/test/integration/clojure-stdlib-suite-test.janet b/test/integration/clojure-stdlib-suite-test.janet index d8f675f..d50fc58 100644 --- a/test/integration/clojure-stdlib-suite-test.janet +++ b/test/integration/clojure-stdlib-suite-test.janet @@ -12,9 +12,10 @@ [["clojure/walk_test/walk.cljc" 34 true] ["clojure/zip_test/zip.cljc" 33 true] ["clojure/data_test/diff.cljc" 61 true] - # clojure.edn is still a stub (no opts/:eof arity, broken set/nil handling); - # tracked separately. Guard its current passing subset so it can't regress. - ["clojure/edn_test/read_string.cljc" 43 false]]) + # clojure.edn now reads via clojure.core/read-string with opts/:eof + nil/blank + # handling. The remaining 3 fails are sets (the reader yields a set FORM, not a + # constructed set) and #uuid — tracked in jolt-b7y. Guard the passing subset. + ["clojure/edn_test/read_string.cljc" 47 false]]) (def root "test/clojure-stdlib") (def per-file-timeout 6)