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
|
|
@ -1678,6 +1678,9 @@
|
|||
(and (struct? v) (= :jolt/uuid (v :jolt/type)))
|
||||
(do (buffer/push-string buf "#uuid \"") (buffer/push-string buf (v :str))
|
||||
(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))
|
||||
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
|
||||
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
|
||||
|
|
@ -1709,6 +1712,7 @@
|
|||
(and (struct? v) (= :symbol (v :jolt/type)))
|
||||
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
|
||||
(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)
|
||||
(number? v) (fmt-number v)
|
||||
(= true v) "true"
|
||||
|
|
|
|||
|
|
@ -1652,9 +1652,9 @@
|
|||
(resolve-sym ctx bindings form)
|
||||
(if (= :jolt/char (form :jolt/type))
|
||||
form
|
||||
# a UUID value flowing back through eval (macro expansion, eval of a read
|
||||
# form) is a self-evaluating literal, like chars.
|
||||
(if (= :jolt/uuid (form :jolt/type))
|
||||
# a UUID/inst value flowing back through eval (macro expansion, eval of a
|
||||
# read form) is a self-evaluating literal, like chars.
|
||||
(if (or (= :jolt/uuid (form :jolt/type)) (= :jolt/inst (form :jolt/type)))
|
||||
form
|
||||
(if (= :jolt/set (form :jolt/type))
|
||||
# evaluate each element (set literals like #{(inc 1)} must compute)
|
||||
|
|
|
|||
|
|
@ -492,9 +492,23 @@
|
|||
[form new-pos] (read-form s end)]
|
||||
[{: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]
|
||||
(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
|
||||
"Normalize a metadata reader form (Clojure semantics): a symbol or string is a
|
||||
|
|
|
|||
|
|
@ -371,6 +371,78 @@
|
|||
(put namespaces ns-sym 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
|
||||
# equality and map-key hashing are case-insensitive by value (struct equality),
|
||||
# matching Clojure (java.util.UUID equality / cljs UUID).
|
||||
|
|
@ -406,7 +478,7 @@
|
|||
:source-paths @["jolt-core" "src/jolt"]
|
||||
:type-registry @{}
|
||||
: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)))
|
||||
dr)}
|
||||
# create the user namespace via a partial context
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue