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 {}]]])
|
@[{:jolt/type :symbol :ns nil :name "ex-info"} msg-form {}]]])
|
||||||
|
|
||||||
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)
|
# 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
|
# update — works on both structs and tables
|
||||||
(defn core-update [m k f & args]
|
(defn core-update [m k f & args]
|
||||||
|
|
@ -3532,6 +3536,7 @@
|
||||||
"defrecord" core-defrecord
|
"defrecord" core-defrecord
|
||||||
"comment" core-comment
|
"comment" core-comment
|
||||||
"resolve" core-resolve
|
"resolve" core-resolve
|
||||||
|
"ns-name" core-ns-name
|
||||||
"update" core-update
|
"update" core-update
|
||||||
"update-in" core-update-in
|
"update-in" core-update-in
|
||||||
"assoc-in" core-assoc-in
|
"assoc-in" core-assoc-in
|
||||||
|
|
|
||||||
|
|
@ -580,8 +580,12 @@
|
||||||
ns (ctx-find-ns ctx ns-name)
|
ns (ctx-find-ns ctx ns-name)
|
||||||
# Create var first (unbound) so self-referencing defs resolve
|
# Create var first (unbound) so self-referencing defs resolve
|
||||||
v (ns-intern ns (name-sym :name))
|
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)
|
(bind-root v val)
|
||||||
|
(when has-doc
|
||||||
|
(put v :meta (merge (or (get v :meta) {}) {:doc (in form 2)})))
|
||||||
(when dynamic?
|
(when dynamic?
|
||||||
(put v :dynamic true))
|
(put v :dynamic true))
|
||||||
# def returns the var (Clojure semantics); REPL prints #'ns/name
|
# 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-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-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))
|
"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))]
|
"in-ns" (let [sym (eval-form ctx bindings (in form 1))
|
||||||
(ctx-set-current-ns ctx ns-name)
|
ns-name (if (and (struct? sym) (= :symbol (sym :jolt/type))) (sym :name) (string sym))]
|
||||||
(ctx-find-ns ctx ns-name)
|
(ctx-find-ns ctx ns-name)
|
||||||
|
(ctx-set-current-ns ctx ns-name)
|
||||||
nil)
|
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] ...)...)
|
"fn*" (let [# optional name: (fn* name [args] ...) / (fn* name ([args] ...)...)
|
||||||
named? (and (struct? (in form 1)) (= :symbol ((in form 1) :jolt/type)))
|
named? (and (struct? (in form 1)) (= :symbol ((in form 1) :jolt/type)))
|
||||||
fn-name (if named? ((in form 1) :name) nil)
|
fn-name (if named? ((in form 1) :name) nil)
|
||||||
|
|
|
||||||
|
|
@ -303,14 +303,15 @@
|
||||||
f))
|
f))
|
||||||
(def replaced (replace-pct form))
|
(def replaced (replace-pct form))
|
||||||
(def arg-names @[])
|
(def arg-names @[])
|
||||||
# Sort %1 %2 %3 ..., then %, then %&
|
# Positional params %, %1, %2, ... in order; %& becomes a `& rest` param.
|
||||||
(def sorted-keys (sort (keys arg-map)))
|
(def pos-keys (sort (filter |(not= $ "%&") (keys arg-map))))
|
||||||
(each k sorted-keys
|
(each k pos-keys
|
||||||
(array/push arg-names {:jolt/type :symbol :ns nil :name (get arg-map k)}))
|
(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*")])
|
(def result @[(sym "fn*")])
|
||||||
(if (> (length arg-names) 0)
|
|
||||||
(array/push result (tuple ;arg-names))
|
(array/push result (tuple ;arg-names))
|
||||||
(array/push result (tuple))) # no args
|
|
||||||
(array/push result replaced)
|
(array/push result replaced)
|
||||||
[result new-pos]))
|
[result new-pos]))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@
|
||||||
|
|
||||||
(print "1: in-ns...")
|
(print "1: in-ns...")
|
||||||
(let [ctx (make-ctx)]
|
(let [ctx (make-ctx)]
|
||||||
(def form (parse-string "(in-ns my.app)"))
|
(def form (parse-string "(in-ns 'my.app)"))
|
||||||
(eval-form ctx @{} form)
|
(eval-form ctx @{} form)
|
||||||
(assert (= "my.app" (ctx-current-ns ctx)) "in-ns switches namespace"))
|
(assert (= "my.app" (ctx-current-ns ctx)) "in-ns switches namespace"))
|
||||||
(print " passed")
|
(print " passed")
|
||||||
|
|
||||||
(print "2: def in different namespace...")
|
(print "2: def in different namespace...")
|
||||||
(let [ctx (make-ctx)]
|
(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)"))
|
(eval-form ctx @{} (parse-string "(def x 42)"))
|
||||||
(let [ns (ctx-find-ns ctx "my.app")
|
(let [ns (ctx-find-ns ctx "my.app")
|
||||||
v (ns-find ns "x")]
|
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