core: #inst instant values + syntax-quote literal collapse (spec 2.3/2.4)
#inst (jolt-rnh): an instant is an immutable tagged struct
{:jolt/type :jolt/inst :ms <epoch-millis>} — equality and map-key hashing by
INSTANT, so different offsets denoting the same moment are =. The reader
parses RFC3339 with Clojure's partial-timestamp defaults (#inst "2020" is
2020-01-01T00:00:00.000Z) and errors on malformed input; inst?/inst-ms in
the overlay; pr-str prints the canonical
#inst "yyyy-MM-ddThh:mm:ss.fff-00:00" and round-trips; str gives the bare
RFC3339 string. Self-evaluating in the evaluator (like uuid/chars).
Syntax-quote (jolt-l2a): a syntax-quoted self-evaluating literal (string,
number, boolean, nil, keyword, char) collapses to the literal at READ time,
matching Clojure's reader — so nested/adjacent backticks over literals are
inert: (= "meow" ```"meow") is true (jank pass-adjacent). Symbols still
qualify and collections still template. General nested syntax-quote over
non-literals remains UNVERIFIED in spec S25.
Tests: inst-spec (18 cases incl. partial defaults, offsets, round-trip),
+10 literal-collapse reader rows, +5 conformance rows (321x3). Spec
02-reader S20/S25 updated to normative. Suite stable 4470/86.
This commit is contained in:
parent
094152a8c3
commit
e58be2fbd2
10 changed files with 169 additions and 17 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
# §2 The Reader (Lexical Syntax)
|
# §2 The Reader (Lexical Syntax)
|
||||||
|
|
||||||
**Status**: token grammar drafted; reader-macro catalog complete with
|
**Status**: token grammar drafted; reader-macro catalog complete with
|
||||||
normative entries; syntax-quote section carries two open divergences.
|
normative entries; #inst and literal-collapse divergences resolved.
|
||||||
Conformance: jolt `reader-forms-spec` + `reader-syntax-spec` (granularity
|
Conformance: jolt `reader-forms-spec` + `reader-syntax-spec` (granularity
|
||||||
model: jank's per-construct corpus, 62 files under
|
model: jank's per-construct corpus, 62 files under
|
||||||
`test/jank/{reader-macro,syntax-quote}` — adapted rows cited per entry).
|
`test/jank/{reader-macro,syntax-quote}` — adapted rows cited per entry).
|
||||||
|
|
@ -150,9 +150,12 @@ false; `(NaN? ##NaN)` is true.
|
||||||
read value. An unknown tag MUST be a read error (jank
|
read value. An unknown tag MUST be a read error (jank
|
||||||
`fail-unsupported-tag`).
|
`fail-unsupported-tag`).
|
||||||
- Built-in tags every implementation MUST provide: `#uuid "…"` → a UUID
|
- Built-in tags every implementation MUST provide: `#uuid "…"` → a UUID
|
||||||
value (§9 `parse-uuid` semantics — round-trips through printing), `#inst
|
value (§9 `parse-uuid` semantics — round-trips through printing), and
|
||||||
"…"` → an instant (representation host-classified; ⚠ jolt currently reads
|
`#inst "…"` → an instant value: RFC3339 with partial-timestamp defaults
|
||||||
`#inst` as its bare string — divergence, filed).
|
(`#inst "2020"` ≡ `#inst "2020-01-01T00:00:00.000-00:00"`), equality by
|
||||||
|
instant (offset-normalized), `inst?`/`inst-ms` (epoch milliseconds), printed
|
||||||
|
canonically as `#inst "yyyy-MM-ddThh:mm:ss.fff-00:00"` and round-tripping. A
|
||||||
|
malformed timestamp MUST be an error.
|
||||||
|
|
||||||
**Conformance** (2.3): jolt `reader-forms-spec` "#() (% %N %&)" + new rows
|
**Conformance** (2.3): jolt `reader-forms-spec` "#() (% %N %&)" + new rows
|
||||||
(symbolic values, stacked discard, conditionals); `uuid-spec` reader-literal
|
(symbolic values, stacked discard, conditionals); `uuid-spec` reader-literal
|
||||||
|
|
@ -176,10 +179,11 @@ resolution:
|
||||||
value; `~'sym` is the idiom for an intentionally-unqualified symbol.
|
value; `~'sym` is the idiom for an intentionally-unqualified symbol.
|
||||||
- S24. Syntax-quote distributes through collection literals (vectors, maps,
|
- S24. Syntax-quote distributes through collection literals (vectors, maps,
|
||||||
sets) — qualification and unquoting apply inside them.
|
sets) — qualification and unquoting apply inside them.
|
||||||
- S25. Nested syntax-quotes expand recursively (quasiquote semantics); a
|
- S25. A syntax-quoted self-evaluating literal is the literal, collapsed at
|
||||||
syntax-quoted self-evaluating literal is the literal: `(= "meow"
|
read time — so nested/adjacent backticks over literals are inert:
|
||||||
```"meow")` is true. ⚠ jolt currently fails S25 (filed) — UNVERIFIED
|
`(= "meow" ```"meow")` is true. General nested syntax-quote over symbols
|
||||||
pending the fix.
|
and collections expands recursively (quasiquote semantics) — that general
|
||||||
|
case remains UNVERIFIED pending dedicated conformance rows.
|
||||||
|
|
||||||
**Conformance**: jolt `reader-forms-spec` "syntax-quote" (gensym, unquote,
|
**Conformance**: jolt `reader-forms-spec` "syntax-quote" (gensym, unquote,
|
||||||
splice) + conformance "syntax-quote fully-qualifies"; jank
|
splice) + conformance "syntax-quote fully-qualifies"; jank
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ community examples). jolt interns 550 of them.
|
||||||
|
|
||||||
| Status | Count | Meaning |
|
| Status | Count | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| implemented+tested | 397 | in jolt and exercised by spec/conformance |
|
| implemented+tested | 399 | in jolt and exercised by spec/conformance |
|
||||||
| implemented-untested | 153 | in jolt, no direct test — spec entries will add them |
|
| 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) |
|
| resolvable-not-interned | 22 | works in code but invisible to ns introspection (conformance finding) |
|
||||||
| missing-portable | 19 | portable semantics, jolt lacks it — implementation gap |
|
| missing-portable | 19 | portable semantics, jolt lacks it — implementation gap |
|
||||||
| special-form | 13 | specified in §3, not a library var |
|
| special-form | 13 | specified in §3, not a library var |
|
||||||
|
|
@ -345,9 +345,9 @@ UNVERIFIED field; that column will be added as entries land.
|
||||||
| `indexed?` | implemented+tested | ✓ |
|
| `indexed?` | implemented+tested | ✓ |
|
||||||
| `infinite?` | implemented+tested | ✓ |
|
| `infinite?` | implemented+tested | ✓ |
|
||||||
| `init-proxy` | implemented-untested | ✓ |
|
| `init-proxy` | implemented-untested | ✓ |
|
||||||
| `inst-ms` | implemented-untested | ✓ |
|
| `inst-ms` | implemented+tested | ✓ |
|
||||||
| `inst-ms*` | missing-portable | |
|
| `inst-ms*` | missing-portable | |
|
||||||
| `inst?` | implemented-untested | ✓ |
|
| `inst?` | implemented+tested | ✓ |
|
||||||
| `instance?` | implemented+tested | ✓ |
|
| `instance?` | implemented+tested | ✓ |
|
||||||
| `int` | implemented+tested | ✓ |
|
| `int` | implemented+tested | ✓ |
|
||||||
| `int-array` | implemented-untested | ✓ |
|
| `int-array` | implemented-untested | ✓ |
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,12 @@
|
||||||
(defn tagged-literal? [x] (= (get x :jolt/type) :jolt/tagged-literal))
|
(defn tagged-literal? [x] (= (get x :jolt/type) :jolt/tagged-literal))
|
||||||
(defn record? [x] (some? (get x :jolt/deftype)))
|
(defn record? [x] (some? (get x :jolt/deftype)))
|
||||||
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
|
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
|
||||||
|
(defn inst? [x] (= (get x :jolt/type) :jolt/inst))
|
||||||
|
|
||||||
|
;; inst-ms: epoch milliseconds of an instant; throws on a non-inst (Clojure
|
||||||
|
;; protocol behavior).
|
||||||
|
(defn inst-ms [x]
|
||||||
|
(if (inst? x) (get x :ms) (throw (str "inst-ms requires an inst, got: " x))))
|
||||||
|
|
||||||
;; Clojure 1.11 map transformers. PHM base so transformed keys canonicalize
|
;; Clojure 1.11 map transformers. PHM base so transformed keys canonicalize
|
||||||
;; (collisions: last entry in seq order wins, matching the reference).
|
;; (collisions: last entry in seq order wins, matching the reference).
|
||||||
|
|
|
||||||
|
|
@ -1678,6 +1678,9 @@
|
||||||
(and (struct? v) (= :jolt/uuid (v :jolt/type)))
|
(and (struct? v) (= :jolt/uuid (v :jolt/type)))
|
||||||
(do (buffer/push-string buf "#uuid \"") (buffer/push-string buf (v :str))
|
(do (buffer/push-string buf "#uuid \"") (buffer/push-string buf (v :str))
|
||||||
(buffer/push-string buf "\""))
|
(buffer/push-string buf "\""))
|
||||||
|
(and (struct? v) (= :jolt/inst (v :jolt/type)))
|
||||||
|
(do (buffer/push-string buf "#inst \"") (buffer/push-string buf (inst->rfc3339 v))
|
||||||
|
(buffer/push-string buf "\""))
|
||||||
(and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v))
|
(and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v))
|
||||||
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
|
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
|
||||||
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
|
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
|
||||||
|
|
@ -1709,6 +1712,7 @@
|
||||||
(and (struct? v) (= :symbol (v :jolt/type)))
|
(and (struct? v) (= :symbol (v :jolt/type)))
|
||||||
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
|
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
|
||||||
(and (struct? v) (= :jolt/uuid (v :jolt/type))) (v :str)
|
(and (struct? v) (= :jolt/uuid (v :jolt/type))) (v :str)
|
||||||
|
(and (struct? v) (= :jolt/inst (v :jolt/type))) (inst->rfc3339 v)
|
||||||
(and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v)
|
(and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v)
|
||||||
(number? v) (fmt-number v)
|
(number? v) (fmt-number v)
|
||||||
(= true v) "true"
|
(= true v) "true"
|
||||||
|
|
|
||||||
|
|
@ -1652,9 +1652,9 @@
|
||||||
(resolve-sym ctx bindings form)
|
(resolve-sym ctx bindings form)
|
||||||
(if (= :jolt/char (form :jolt/type))
|
(if (= :jolt/char (form :jolt/type))
|
||||||
form
|
form
|
||||||
# a UUID value flowing back through eval (macro expansion, eval of a read
|
# a UUID/inst value flowing back through eval (macro expansion, eval of a
|
||||||
# form) is a self-evaluating literal, like chars.
|
# read form) is a self-evaluating literal, like chars.
|
||||||
(if (= :jolt/uuid (form :jolt/type))
|
(if (or (= :jolt/uuid (form :jolt/type)) (= :jolt/inst (form :jolt/type)))
|
||||||
form
|
form
|
||||||
(if (= :jolt/set (form :jolt/type))
|
(if (= :jolt/set (form :jolt/type))
|
||||||
# evaluate each element (set literals like #{(inc 1)} must compute)
|
# evaluate each element (set literals like #{(inc 1)} must compute)
|
||||||
|
|
|
||||||
|
|
@ -492,9 +492,23 @@
|
||||||
[form new-pos] (read-form s end)]
|
[form new-pos] (read-form s end)]
|
||||||
[{:jolt/type :jolt/tagged :tag (keyword tag) :form form} new-pos]))))
|
[{:jolt/type :jolt/tagged :tag (keyword tag) :form form} new-pos]))))
|
||||||
|
|
||||||
|
(defn- self-evaluating-literal?
|
||||||
|
"True for forms syntax-quote passes through unchanged: strings, numbers,
|
||||||
|
booleans, nil, keywords, and character literals. NOT symbols (they qualify)
|
||||||
|
or collections (they template)."
|
||||||
|
[form]
|
||||||
|
(or (nil? form) (= true form) (= false form) (number? form)
|
||||||
|
(string? form) (buffer? form) (keyword? form)
|
||||||
|
(and (struct? form) (= :jolt/char (form :jolt/type)))))
|
||||||
|
|
||||||
(defn read-quote [s pos new-pos token-sym]
|
(defn read-quote [s pos new-pos token-sym]
|
||||||
(let [[form final-pos] (read-form s new-pos)]
|
(let [[form final-pos] (read-form s new-pos)]
|
||||||
[(array token-sym form) final-pos]))
|
# Spec 02-reader S25: syntax-quote of a self-evaluating literal is the
|
||||||
|
# literal, collapsed at READ time (matching Clojure's reader) — so nested
|
||||||
|
# backticks over literals are inert: ```"meow" reads as "meow".
|
||||||
|
(if (and (= "syntax-quote" (token-sym :name)) (self-evaluating-literal? form))
|
||||||
|
[form final-pos]
|
||||||
|
[(array token-sym form) final-pos])))
|
||||||
|
|
||||||
(defn- meta-form->map
|
(defn- meta-form->map
|
||||||
"Normalize a metadata reader form (Clojure semantics): a symbol or string is a
|
"Normalize a metadata reader form (Clojure semantics): a symbol or string is a
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,78 @@
|
||||||
(put namespaces ns-sym ns)
|
(put namespaces ns-sym ns)
|
||||||
ns))))
|
ns))))
|
||||||
|
|
||||||
|
# Instant value: an immutable tagged struct keyed by epoch milliseconds, so
|
||||||
|
# equality and map-key hashing are by INSTANT (offset-normalized): two #inst
|
||||||
|
# literals with different offsets denoting the same moment are =.
|
||||||
|
(defn make-inst [ms]
|
||||||
|
{:jolt/type :jolt/inst :ms ms})
|
||||||
|
|
||||||
|
(defn parse-inst
|
||||||
|
"Parse an RFC3339 timestamp with Clojure's partial defaults
|
||||||
|
(yyyy[-MM[-dd[Thh[:mm[:ss[.fff]]]]]][Z|+hh:mm|-hh:mm]) to an inst value.
|
||||||
|
Errors on a malformed timestamp."
|
||||||
|
[ts]
|
||||||
|
(def pat (peg/compile
|
||||||
|
~(sequence
|
||||||
|
(capture (repeat 4 :d)) # year
|
||||||
|
(opt (sequence "-" (capture (repeat 2 :d)))) # month
|
||||||
|
(opt (sequence "-" (capture (repeat 2 :d)))) # day
|
||||||
|
(opt (sequence "T" (capture (repeat 2 :d)) # hour
|
||||||
|
(opt (sequence ":" (capture (repeat 2 :d)) # min
|
||||||
|
(opt (sequence ":" (capture (repeat 2 :d)) # sec
|
||||||
|
(opt (sequence "." (capture (some :d)))))))))) # frac
|
||||||
|
(opt (choice (capture "Z")
|
||||||
|
(sequence (capture (set "+-")) (capture (repeat 2 :d))
|
||||||
|
":" (capture (repeat 2 :d)))))
|
||||||
|
-1)))
|
||||||
|
(def m (peg/match pat ts))
|
||||||
|
(when (nil? m) (error (string "Unrecognized #inst timestamp: " ts)))
|
||||||
|
# captures arrive positionally; classify by shape: digits runs + offset parts.
|
||||||
|
(var year nil) (var month 1) (var day 1)
|
||||||
|
(var hh 0) (var mm 0) (var ss 0) (var frac "0")
|
||||||
|
(var off-sign nil) (var off-h 0) (var off-m 0)
|
||||||
|
(var i 0)
|
||||||
|
(def fields @[:year :month :day :hh :mm :ss])
|
||||||
|
(var fi 0)
|
||||||
|
(while (< i (length m))
|
||||||
|
(def part (in m i))
|
||||||
|
(cond
|
||||||
|
(= part "Z") nil
|
||||||
|
(or (= part "+") (= part "-"))
|
||||||
|
(do (set off-sign part)
|
||||||
|
(set off-h (scan-number (in m (+ i 1))))
|
||||||
|
(set off-m (scan-number (in m (+ i 2))))
|
||||||
|
(+= i 2))
|
||||||
|
# fractional seconds arrive right after :ss was filled
|
||||||
|
(and (>= fi 6))
|
||||||
|
(set frac part)
|
||||||
|
(do
|
||||||
|
(def v (scan-number part))
|
||||||
|
(case (in fields fi)
|
||||||
|
:year (set year v) :month (set month v) :day (set day v)
|
||||||
|
:hh (set hh v) :mm (set mm v) :ss (set ss v))
|
||||||
|
(++ fi)))
|
||||||
|
(++ i))
|
||||||
|
(when (nil? year) (error (string "Unrecognized #inst timestamp: " ts)))
|
||||||
|
(def base-s (os/mktime {:year year :month (- month 1) :month-day (- day 1)
|
||||||
|
:hours hh :minutes mm :seconds ss}))
|
||||||
|
# fractional part -> milliseconds (truncate beyond 3 digits)
|
||||||
|
(def frac3 (string/slice (string frac "000") 0 3))
|
||||||
|
(def ms-frac (scan-number frac3))
|
||||||
|
(def off-s (* (if (= off-sign "-") -1 1) (+ (* off-h 3600) (* off-m 60))))
|
||||||
|
(make-inst (- (+ (* base-s 1000) ms-frac) (* off-s 1000))))
|
||||||
|
|
||||||
|
(defn inst->rfc3339
|
||||||
|
"Canonical print form: yyyy-MM-ddThh:mm:ss.fff-00:00 (UTC, like Clojure)."
|
||||||
|
[inst]
|
||||||
|
(def ms (inst :ms))
|
||||||
|
(def s (math/floor (/ ms 1000)))
|
||||||
|
(def frac (- ms (* s 1000)))
|
||||||
|
(def d (os/date s))
|
||||||
|
(string/format "%04d-%02d-%02dT%02d:%02d:%02d.%03d-00:00"
|
||||||
|
(d :year) (+ 1 (d :month)) (+ 1 (d :month-day))
|
||||||
|
(d :hours) (d :minutes) (d :seconds) frac))
|
||||||
|
|
||||||
# UUID value: an immutable tagged struct. Lowercased at construction so
|
# UUID value: an immutable tagged struct. Lowercased at construction so
|
||||||
# equality and map-key hashing are case-insensitive by value (struct equality),
|
# equality and map-key hashing are case-insensitive by value (struct equality),
|
||||||
# matching Clojure (java.util.UUID equality / cljs UUID).
|
# matching Clojure (java.util.UUID equality / cljs UUID).
|
||||||
|
|
@ -406,7 +478,7 @@
|
||||||
:source-paths @["jolt-core" "src/jolt"]
|
:source-paths @["jolt-core" "src/jolt"]
|
||||||
:type-registry @{}
|
:type-registry @{}
|
||||||
:data-readers (let [dr @{}]
|
:data-readers (let [dr @{}]
|
||||||
(put dr (keyword "#inst") (fn [s] s))
|
(put dr (keyword "#inst") (fn [s] (parse-inst s)))
|
||||||
(put dr (keyword "#uuid") (fn [s] (make-uuid s)))
|
(put dr (keyword "#uuid") (fn [s] (make-uuid s)))
|
||||||
dr)}
|
dr)}
|
||||||
# create the user namespace via a partial context
|
# create the user namespace via a partial context
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,13 @@
|
||||||
["macroexpand" "true" "(= (quote if) (first (macroexpand (quote (when-not false 1)))))"]
|
["macroexpand" "true" "(= (quote if) (first (macroexpand (quote (when-not false 1)))))"]
|
||||||
["require bare symbol" "\"a,b\"" "(do (require (quote clojure.string)) (clojure.string/join \",\" [\"a\" \"b\"]))"]
|
["require bare symbol" "\"a,b\"" "(do (require (quote clojure.string)) (clojure.string/join \",\" [\"a\" \"b\"]))"]
|
||||||
["ns-publics lookup" "true" "(do (def cnp 7) (some? (get (ns-publics (quote user)) (quote cnp))))"]
|
["ns-publics lookup" "true" "(do (def cnp 7) (some? (get (ns-publics (quote user)) (quote cnp))))"]
|
||||||
|
|
||||||
|
### ---- #inst + syntax-quote literal collapse (spec 2.4/2.3) ----
|
||||||
|
["inst? + inst-ms" "0" "(inst-ms #inst \"1970-01-01T00:00:00Z\")"]
|
||||||
|
["inst partial = full" "true" "(= #inst \"2020\" #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["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"]
|
||||||
["macroexpand-1 when" "2" "(count (rest (macroexpand-1 (quote (when true 1)))))"]
|
["macroexpand-1 when" "2" "(count (rest (macroexpand-1 (quote (when true 1)))))"]
|
||||||
|
|
||||||
### ---- HIGH: aliased namespace calls ----
|
### ---- HIGH: aliased namespace calls ----
|
||||||
|
|
|
||||||
30
test/spec/inst-spec.janet
Normal file
30
test/spec/inst-spec.janet
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Specification: #inst literals — instants as values (spec 02-reader S20).
|
||||||
|
# An instant is an immutable tagged struct {:jolt/type :jolt/inst :ms <epoch-ms>}:
|
||||||
|
# value equality by instant (offset-normalized), usable as map keys.
|
||||||
|
# The reader accepts RFC3339 with Clojure's partial-timestamp defaults
|
||||||
|
# (#inst "2020" == #inst "2020-01-01T00:00:00.000-00:00").
|
||||||
|
(use ../support/harness)
|
||||||
|
|
||||||
|
(defspec "inst / reading & identity"
|
||||||
|
["reads to inst" "true" "(inst? #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["inst? false on string" "false" "(inst? \"2020-01-01\")"]
|
||||||
|
["epoch zero" "0" "(inst-ms #inst \"1970-01-01T00:00:00Z\")"]
|
||||||
|
["one second" "1000" "(inst-ms #inst \"1970-01-01T00:00:01Z\")"]
|
||||||
|
["millis" "123" "(inst-ms #inst \"1970-01-01T00:00:00.123Z\")"]
|
||||||
|
["a real date" "1577836800000" "(inst-ms #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["inst-ms throws on non-inst" :throws "(inst-ms 42)"])
|
||||||
|
|
||||||
|
(defspec "inst / partial timestamps & offsets"
|
||||||
|
["year only" "true" "(= #inst \"2020\" #inst \"2020-01-01T00:00:00.000Z\")"]
|
||||||
|
["year-month" "true" "(= #inst \"2020-03\" #inst \"2020-03-01T00:00:00Z\")"]
|
||||||
|
["date only" "true" "(= #inst \"2020-03-15\" #inst \"2020-03-15T00:00:00Z\")"]
|
||||||
|
["positive offset" "true" "(= #inst \"2020-01-01T01:00:00+01:00\" #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["negative offset" "true" "(= #inst \"2019-12-31T23:00:00-01:00\" #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["-00:00 offset" "true" "(= #inst \"2020-01-01T00:00:00-00:00\" #inst \"2020-01-01T00:00:00Z\")"]
|
||||||
|
["bad timestamp throws" :throws "#inst \"garbage\""])
|
||||||
|
|
||||||
|
(defspec "inst / value semantics & printing"
|
||||||
|
["equal by instant" "true" "(= #inst \"2020-01-01T00:00:00Z\" #inst \"2020-01-01T00:00:00.000Z\")"]
|
||||||
|
["unequal instants" "false" "(= #inst \"2020-01-01T00:00:00Z\" #inst \"2020-01-01T00:00:01Z\")"]
|
||||||
|
["works as map key" ":v" "(get {#inst \"2020-01-01T00:00:00Z\" :v} #inst \"2020-01-01T00:00:00.000Z\")"]
|
||||||
|
["pr-str round-trips" "\"#inst \\\"2020-01-01T00:00:00.000-00:00\\\"\"" "(pr-str #inst \"2020-01-01T00:00:00Z\")"])
|
||||||
|
|
@ -63,3 +63,18 @@
|
||||||
["var-quote qualified" "true" "(= (var clojure.core/str) #'clojure.core/str)"]
|
["var-quote qualified" "true" "(= (var clojure.core/str) #'clojure.core/str)"]
|
||||||
["gensym stable in template" "true" "(let [syms `[meow# meow#]] (= (first syms) (second syms)))"]
|
["gensym stable in template" "true" "(let [syms `[meow# meow#]] (= (first syms) (second syms)))"]
|
||||||
["gensym fresh across templates" "false" "(= `meow# `meow#)"])
|
["gensym fresh across templates" "false" "(= `meow# `meow#)"])
|
||||||
|
|
||||||
|
# Spec 02-reader S25: syntax-quote of a self-evaluating literal is the literal
|
||||||
|
# (read-time collapse), so adjacent/nested backticks over literals are inert.
|
||||||
|
(defspec "reader / syntax-quote literal collapse (spec 2.4 S25)"
|
||||||
|
["string once" "true" "(= \"meow\" `\"meow\")"]
|
||||||
|
["string nested" "true" "(= \"meow\" ``\"meow\")"]
|
||||||
|
["string triple" "true" "(= \"meow\" ```\"meow\")"]
|
||||||
|
["number nested" "true" "(= 42 ``42)"]
|
||||||
|
["keyword nested" "true" "(= :k ``:k)"]
|
||||||
|
["nil nested" "true" "(nil? ``nil)"]
|
||||||
|
["char nested" "true" "(= \\a ``\\a)"]
|
||||||
|
["bool nested" "true" "(= true ``true)"]
|
||||||
|
# collapse must NOT apply to symbols (they qualify) or collections (templates)
|
||||||
|
["symbol still qualifies" "true" "(= (quote clojure.core/map) `map)"]
|
||||||
|
["vector still templates" "true" "(= [1 2] `[1 ~(inc 1)])"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue