test(spec): macros, reader, host-interop, namespaces — fix def-doc, resolve, %&
Final spec areas. Bugs caught and fixed: - (def name docstring value) used the docstring as the value; now the 3-arg docstring form binds the value and records :doc meta - resolve was a nil stub; now a special form resolving a symbol to its var (nil if unresolved). Added find-ns (non-creating lookup) and ns-name. - in-ns didn't evaluate its arg, so (in-ns 'foo) failed; now evaluates it per Clojure (the integration test's unquoted form updated to the quoted idiom) - #(... %& ...) built %& as a positional param instead of a & rest param; now emits (fn* [... & gen] ...) so %& captures the rest Full public-API spec layer now in place. conformance 218/218, jpm test green.
This commit is contained in:
parent
e3d2aa8d14
commit
ad5539b0de
8 changed files with 140 additions and 12 deletions
|
|
@ -2432,7 +2432,11 @@
|
|||
@[{:jolt/type :symbol :ns nil :name "ex-info"} msg-form {}]]])
|
||||
|
||||
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)
|
||||
(defn core-resolve [sym] nil)
|
||||
(defn core-resolve [sym] nil) # shadowed by the resolve special form (needs ctx)
|
||||
(defn core-ns-name [ns]
|
||||
# ns object -> its name as a symbol (works whether ns is a table/struct/phm)
|
||||
(let [nm (core-get ns :name)]
|
||||
(if nm {:jolt/type :symbol :ns nil :name (string nm)} nil)))
|
||||
|
||||
# update — works on both structs and tables
|
||||
(defn core-update [m k f & args]
|
||||
|
|
@ -3532,6 +3536,7 @@
|
|||
"defrecord" core-defrecord
|
||||
"comment" core-comment
|
||||
"resolve" core-resolve
|
||||
"ns-name" core-ns-name
|
||||
"update" core-update
|
||||
"update-in" core-update-in
|
||||
"assoc-in" core-assoc-in
|
||||
|
|
|
|||
|
|
@ -580,8 +580,12 @@
|
|||
ns (ctx-find-ns ctx ns-name)
|
||||
# Create var first (unbound) so self-referencing defs resolve
|
||||
v (ns-intern ns (name-sym :name))
|
||||
val (eval-form ctx bindings (in form 2))]
|
||||
# (def name docstring value): docstring is form 2, value form 3
|
||||
has-doc (and (> (length form) 3) (string? (in form 2)))
|
||||
val (eval-form ctx bindings (in form (if has-doc 3 2)))]
|
||||
(bind-root v val)
|
||||
(when has-doc
|
||||
(put v :meta (merge (or (get v :meta) {}) {:doc (in form 2)})))
|
||||
(when dynamic?
|
||||
(put v :dynamic true))
|
||||
# def returns the var (Clojure semantics); REPL prints #'ns/name
|
||||
|
|
@ -695,10 +699,19 @@
|
|||
"ns-aliases" (let [ns (ctx-find-ns ctx (ctx-current-ns ctx))] (ns :aliases))
|
||||
"ns-imports" (let [ns (ctx-find-ns ctx (ctx-current-ns ctx))] (ns :imports))
|
||||
"ns-resolve" (ns-resolve (ctx-find-ns ctx (ctx-current-ns ctx)) (in form 1))
|
||||
"in-ns" (let [ns-name (sym-name-str (in form 1))]
|
||||
(ctx-set-current-ns ctx ns-name)
|
||||
"in-ns" (let [sym (eval-form ctx bindings (in form 1))
|
||||
ns-name (if (and (struct? sym) (= :symbol (sym :jolt/type))) (sym :name) (string sym))]
|
||||
(ctx-find-ns ctx ns-name)
|
||||
(ctx-set-current-ns ctx ns-name)
|
||||
nil)
|
||||
"resolve" (let [sym (eval-form ctx bindings (in form 1))]
|
||||
(if (and (struct? sym) (= :symbol (sym :jolt/type)))
|
||||
(let [r (protect (resolve-var ctx bindings sym))]
|
||||
(if (= (r 0) true) (r 1) nil))
|
||||
nil))
|
||||
"find-ns" (let [sym (eval-form ctx bindings (in form 1))
|
||||
nm (if (and (struct? sym) (= :symbol (sym :jolt/type))) (sym :name) (string sym))]
|
||||
(get (get (ctx :env) :namespaces) nm))
|
||||
"fn*" (let [# optional name: (fn* name [args] ...) / (fn* name ([args] ...)...)
|
||||
named? (and (struct? (in form 1)) (= :symbol ((in form 1) :jolt/type)))
|
||||
fn-name (if named? ((in form 1) :name) nil)
|
||||
|
|
|
|||
|
|
@ -303,14 +303,15 @@
|
|||
f))
|
||||
(def replaced (replace-pct form))
|
||||
(def arg-names @[])
|
||||
# Sort %1 %2 %3 ..., then %, then %&
|
||||
(def sorted-keys (sort (keys arg-map)))
|
||||
(each k sorted-keys
|
||||
# Positional params %, %1, %2, ... in order; %& becomes a `& rest` param.
|
||||
(def pos-keys (sort (filter |(not= $ "%&") (keys arg-map))))
|
||||
(each k pos-keys
|
||||
(array/push arg-names {:jolt/type :symbol :ns nil :name (get arg-map k)}))
|
||||
(when (get arg-map "%&")
|
||||
(array/push arg-names (sym "&"))
|
||||
(array/push arg-names {:jolt/type :symbol :ns nil :name (get arg-map "%&")}))
|
||||
(def result @[(sym "fn*")])
|
||||
(if (> (length arg-names) 0)
|
||||
(array/push result (tuple ;arg-names))
|
||||
(array/push result (tuple))) # no args
|
||||
(array/push result (tuple ;arg-names))
|
||||
(array/push result replaced)
|
||||
[result new-pos]))
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@
|
|||
|
||||
(print "1: in-ns...")
|
||||
(let [ctx (make-ctx)]
|
||||
(def form (parse-string "(in-ns my.app)"))
|
||||
(def form (parse-string "(in-ns 'my.app)"))
|
||||
(eval-form ctx @{} form)
|
||||
(assert (= "my.app" (ctx-current-ns ctx)) "in-ns switches namespace"))
|
||||
(print " passed")
|
||||
|
||||
(print "2: def in different namespace...")
|
||||
(let [ctx (make-ctx)]
|
||||
(eval-form ctx @{} (parse-string "(in-ns my.app)"))
|
||||
(eval-form ctx @{} (parse-string "(in-ns 'my.app)"))
|
||||
(eval-form ctx @{} (parse-string "(def x 42)"))
|
||||
(let [ns (ctx-find-ns ctx "my.app")
|
||||
v (ns-find ns "x")]
|
||||
|
|
|
|||
17
test/spec/host-interop-spec.janet
Normal file
17
test/spec/host-interop-spec.janet
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Specification: host (Janet) interop — the `.` forms and jolt.interop.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "interop / dot forms"
|
||||
["method call" "\"v=41\""
|
||||
"(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)"]
|
||||
["method with args" "\"Hello Alice\""
|
||||
"(. {:greet (fn [self n] (str \"Hello \" n))} greet \"Alice\")"]
|
||||
["field access .-" "41" "(.-value {:value 41})"]
|
||||
["dot field keyword" "41" "(. {:value 41} :value)"])
|
||||
|
||||
(defspec "interop / jolt.interop"
|
||||
["janet-type quoted list" ":array" "(do (require (quote [jolt.interop :as j])) (j/janet-type (quote (1 2))))"]
|
||||
["janet-type list" ":array" "(do (require (quote [jolt.interop :as j])) (j/janet-type (list 1 2)))"]
|
||||
["janet-type string" ":string" "(do (require (quote [jolt.interop :as j])) (j/janet-type \"x\"))"]
|
||||
["janet-type number" ":number" "(do (require (quote [jolt.interop :as j])) (j/janet-type 1))"]
|
||||
["janet-type keyword" ":keyword" "(do (require (quote [jolt.interop :as j])) (j/janet-type :a))"])
|
||||
27
test/spec/macros-spec.janet
Normal file
27
test/spec/macros-spec.janet
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Specification: macros, quoting and syntax-quote.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "macros / quoting"
|
||||
["quote symbol" "(quote a)" "(quote a)"]
|
||||
["quote list" "[1 2 3]" "(quote (1 2 3))"]
|
||||
["quote nested" "[1 [2 3]]" "(quote (1 (2 3)))"]
|
||||
["quote sugar" "'a" "'a"]
|
||||
["syntax-quote literal" "[1 2]" "`[1 2]"]
|
||||
["unquote" "[1 2 3]" "(let [x 2] `[1 ~x 3])"]
|
||||
["unquote-splicing" "[1 2 3 4]" "(let [xs [2 3]] `[1 ~@xs 4])"]
|
||||
["unquote in list" "[3]" "(let [x 3] `(~x))"]
|
||||
["syntax-quote symbol qualifies" "true" "(symbol? `foo)"])
|
||||
|
||||
(defspec "macros / defmacro"
|
||||
["simple macro" "3"
|
||||
"(do (defmacro m [a b] `(+ ~a ~b)) (m 1 2))"]
|
||||
["macro unless" "1"
|
||||
"(do (defmacro unless [c body] `(if ~c nil ~body)) (unless false 1))"]
|
||||
["macro with body splice" "6"
|
||||
"(do (defmacro msum [& xs] `(+ ~@xs)) (msum 1 2 3))"]
|
||||
["macroexpand-1" "true"
|
||||
"(do (defmacro m [x] `(inc ~x)) (list? (macroexpand-1 '(m 5))))"]
|
||||
["gensym unique" "false"
|
||||
"(= (gensym) (gensym))"]
|
||||
["gensym# in template" "true"
|
||||
"(do (defmacro m [] `(let [x# 1] x#)) (= 1 (m)))"])
|
||||
27
test/spec/namespaces-spec.janet
Normal file
27
test/spec/namespaces-spec.janet
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Specification: namespaces, vars and require.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "namespaces / def & vars"
|
||||
["def + deref" "5" "(do (def x 5) x)"]
|
||||
["def returns var" "true" "(var? (def y 1))"]
|
||||
["declare then def" "2" "(do (declare z) (def z 2) z)"]
|
||||
["var special form" "true" "(var? (var +))"]
|
||||
["var sugar #'" "true" "(var? #'+)"]
|
||||
["var-get" "5" "(do (def w 5) (var-get #'w))"]
|
||||
["defn defines fn" "3" "(do (defn f [x] (inc x)) (f 2))"]
|
||||
["def with docstring" "7" "(do (def d \"a doc\" 7) d)"])
|
||||
|
||||
(defspec "namespaces / ns operations"
|
||||
["in-ns switches" "true" "(do (in-ns 'my.ns) (symbol? 'x))"]
|
||||
["ns-name" "true" "(do (require (quote [clojure.string])) (= 'clojure.string (ns-name (find-ns 'clojure.string))))"]
|
||||
["find-ns existing" "true" "(some? (find-ns 'clojure.core))"]
|
||||
["find-ns missing" "nil" "(find-ns 'does.not.exist)"]
|
||||
["resolve var" "true" "(var? (resolve '+))"]
|
||||
["resolve missing" "nil" "(resolve 'totally-undefined-xyz)"])
|
||||
|
||||
(defspec "namespaces / require & refer"
|
||||
["require :as" "\"AB\"" "(do (require '[clojure.string :as s]) (s/upper-case \"ab\"))"]
|
||||
["require :refer" "true" "(do (require '[clojure.string :refer [blank?]]) (blank? \"\"))"]
|
||||
["require :as + :refer" "true" "(do (require '[clojure.string :as s :refer [blank?]]) (and (blank? \"\") (= \"X\" (s/upper-case \"x\"))))"]
|
||||
["require clojure.set" "#{1 2 3}" "(do (require '[clojure.set :as set]) (set/union #{1 2} #{3}))"]
|
||||
["require clojure.walk" "{:a 2}" "(do (require '[clojure.walk :as w]) (w/postwalk (fn [x] (if (number? x) (inc x) x)) {:a 1}))"])
|
||||
38
test/spec/reader-syntax-spec.janet
Normal file
38
test/spec/reader-syntax-spec.janet
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Specification: reader syntax & literals.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "reader / scalar literals"
|
||||
["integer" "42" "42"]
|
||||
["negative" "-7" "-7"]
|
||||
["float" "1.5" "1.5"]
|
||||
["string" "\"hi\"" "\"hi\""]
|
||||
["boolean true" "true" "true"]
|
||||
["nil" "nil" "nil"]
|
||||
["keyword" ":a" ":a"]
|
||||
["namespaced keyword" "true" "(= :a/b :a/b)"]
|
||||
["char" "\\a" "\\a"]
|
||||
["char newline" "true" "(= \\newline (first \"\\n\"))"]
|
||||
["ratio not supported but reads ints" "3" "3"]
|
||||
["hex literal" "255" "0xff"]
|
||||
["symbol via quote" "'foo" "'foo"])
|
||||
|
||||
(defspec "reader / collection literals"
|
||||
["vector" "[1 2 3]" "[1 2 3]"]
|
||||
["list quoted" "[1 2 3]" "'(1 2 3)"]
|
||||
["map" "{:a 1}" "{:a 1}"]
|
||||
["set" "#{1 2 3}" "#{1 2 3}"]
|
||||
["nested" "{:a [1 {:b 2}]}" "{:a [1 {:b 2}]}"]
|
||||
["empty vector" "[]" "[]"]
|
||||
["empty map" "{}" "{}"]
|
||||
["empty set" "#{}" "#{}"])
|
||||
|
||||
(defspec "reader / dispatch & sugar"
|
||||
["anon fn #()" "3" "(#(+ %1 %2) 1 2)"]
|
||||
["anon fn single %" "2" "(#(inc %) 1)"]
|
||||
["anon fn %&" "[2 3]" "(#(vec %&) 2 3)"]
|
||||
["discard #_" "[1 3]" "[1 #_2 3]"]
|
||||
["regex literal" "true" "(= \"abc\" (re-find #\"abc\" \"xabcx\"))"]
|
||||
["reader conditional" "1" "#?(:clj 1 :cljs 2 :default 3)"]
|
||||
["tagged literal var" "true" "(var? #'+)"]
|
||||
["deref sugar" "5" "(let [a (atom 5)] @a)"]
|
||||
["meta sugar" "{:t 1}" "(meta ^{:t 1} [])"])
|
||||
Loading…
Add table
Add a link
Reference in a new issue