Skip to content

Commit c8cf3a9

Browse files
committed
Add Gloat/Glojure build for libyamlstar
Replace GraalVM native-image with Gloat (Glojure AOT → Go → c-shared) for the shared library build: - Replace Java interop with Go interop in core Clojure files - prelude.clj: os.Getenv, fmt.Sprintf, fmt.Fprintln - parser.clj: TRACE uses os.Getenv comparison - receiver.clj: strconv.ParseInt for hex/Unicode escapes - constructor.clj: strconv.ParseInt/ParseFloat, math.Inf/NaN - Fix debug env checks: (not-empty (os.Getenv key)) so empty string is treated as falsy - Fix empty-mapping construction: use reduce/partition instead of array-map which fails in Glojure - Add libyamlstar.clj: Gloat EXPORT bridge replacing gen-class and GraalVM @centrypoint, exports yamlstar_load and yamlstar_version as C-compatible functions
1 parent 4ce61ea commit c8cf3a9

6 files changed

Lines changed: 93 additions & 40 deletions

File tree

core/src/yamlstar/constructor.clj

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,23 @@
1010
Each constructor takes a node and returns native Clojure data.
1111
Supports both short form (!!null) and fully qualified (tag:yaml.org,2002:null) tags."
1212
(let [null-fn (fn [_node] nil)
13-
bool-fn (fn [node] (Boolean/parseBoolean (:value node)))
14-
int-fn (fn [node] (Long/parseLong (:value node)))
13+
bool-fn (fn [node]
14+
(contains? #{"true" "True" "TRUE"} (:value node)))
15+
int-fn (fn [node]
16+
(let [[n _] (strconv.ParseInt (:value node) 10 64)]
17+
n))
1518
float-fn (fn [node]
1619
(let [value (:value node)]
1720
(cond
1821
(re-matches #"[+-]?\.inf|\.Inf|\.INF" value)
19-
(if (= (first value) \-) Double/NEGATIVE_INFINITY Double/POSITIVE_INFINITY)
22+
(if (= (first value) \-) (math.Inf -1) (math.Inf 1))
2023

2124
(re-matches #"\.nan|\.NaN|\.NAN" value)
22-
Double/NaN
25+
(math.NaN)
2326

2427
:else
25-
(Double/parseDouble value))))
28+
(let [[f _] (strconv.ParseFloat value 64)]
29+
f))))
2630
str-fn (fn [node] (:value node))]
2731
{"!!null" null-fn
2832
"tag:yaml.org,2002:null" null-fn
@@ -65,7 +69,7 @@
6569
(construct-node val-node anchors)))
6670
[]
6771
pairs)]
68-
(apply array-map entries))
72+
(reduce (fn [m [k v]] (assoc m k v)) {} (partition 2 entries)))
6973

7074
:sequence
7175
(let [items (:value node)]

core/src/yamlstar/parser/grammar.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@
272272
"\uFFFD"
273273
),
274274
(p/rng parser
275-
(String. (int-array [0x010000]) 0 1),
276-
(String. (int-array [0x10FFFF]) 0 1)
275+
"𐀀",
276+
"􏿿"
277277
)
278278
))
279279
{:trace "c_printable" :name "c_printable"}))
@@ -291,7 +291,7 @@
291291
),
292292
(p/rng parser
293293
"\u0020",
294-
(String. (int-array [0x10FFFF]) 0 1)
294+
"􏿿"
295295
)
296296
))
297297
{:trace "nb_json" :name "nb_json"}))

core/src/yamlstar/parser/parser.clj

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
(declare auto-detect auto-detect-indent trace-start trace-flush)
77

88
;; TRACE flag from environment
9-
(def TRACE (Boolean/parseBoolean (or (env "TRACE") "false")))
9+
(def TRACE (= (os.Getenv "TRACE") "true"))
1010

1111
;; Default state when stack is empty
1212
(def default-state
@@ -202,8 +202,13 @@
202202
(or (end-of-stream* parser)
203203
(and (:doc (state-curr parser))
204204
(start-of-line* parser)
205-
(let [remaining (subs @(:input parser) @(:pos parser))]
206-
(re-find #"^(?:---|\.\.\.)(?=\s|$)" remaining)))))
205+
;; RE2 doesn't support lookaheads; check the char after manually
206+
(let [remaining (subs @(:input parser) @(:pos parser))
207+
prefix (re-find #"^(?:---|\.\.\.)" remaining)]
208+
(when prefix
209+
(let [after (subs remaining (count prefix))]
210+
(or (empty? after)
211+
(re-find #"^\s" after))))))))
207212

208213
;; Grammar-callable versions (return functions)
209214
(defn start-of-line [parser]
@@ -240,9 +245,9 @@
240245
remaining (subs input pos)
241246
pattern (re-pattern (str "^[" low "-" high "]"))]
242247
(when (re-find pattern remaining)
243-
;; Handle surrogate pairs
244-
(let [cp (.codePointAt remaining 0)]
245-
(when (> cp 65535)
248+
;; Handle multi-byte runes (Go uses UTF-8, advance by rune width)
249+
(let [[r _] (unicode:utf8.DecodeRuneInString remaining)]
250+
(when (> (int r) 65535)
246251
(swap! (:pos p) inc)))
247252
(swap! (:pos p) inc)
248253
true))))

core/src/yamlstar/parser/prelude.clj

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
(ns yamlstar.parser.prelude
22
(:require [clojure.string :as str]))
33

4-
;; Environment access
4+
;; Environment access - returns nil (falsy) when not set
55
(defn env [key]
6-
(System/getenv key))
6+
(not-empty (os.Getenv key)))
77

88
;; Type checking predicates
99
(defn is-null? [x] (nil? x))
10-
(defn is-boolean? [x] (instance? Boolean x))
10+
(defn is-boolean? [x] (or (true? x) (false? x)))
1111
(defn is-number? [x] (number? x))
1212
(defn is-string? [x] (string? x))
1313
(defn is-function? [x] (fn? x))
@@ -17,15 +17,15 @@
1717
(defn typeof* [value]
1818
(cond
1919
(nil? value) "null"
20-
(instance? Boolean value) "boolean"
20+
(or (true? value) (false? value)) "boolean"
2121
(number? value) "number"
2222
(string? value) "string"
2323
(keyword? value) "string" ;; Keywords treated as strings
2424
(symbol? value) "string" ;; Symbols treated as strings
2525
(fn? value) "function"
2626
(or (vector? value) (seq? value)) "array"
2727
(map? value) "object"
28-
:else (throw (ex-info "Unknown type" {:value value :type (type value)}))))
28+
:else (throw (ex-info "Unknown type" {:value value}))))
2929

3030
;; String helpers
3131
(defn stringify [o]
@@ -38,18 +38,17 @@
3838
:else (pr-str o)))
3939

4040
(defn hex-char [chr]
41-
(format "%x" (int (first chr))))
41+
(fmt.Sprintf "%x" (int (first chr))))
4242

4343
;; Debug and error functions
4444
(defn warn [msg]
45-
(binding [*out* *err*]
46-
(println msg)))
45+
(fmt.Fprintln os.Stderr msg))
4746

4847
(defn die [msg]
4948
(throw (ex-info msg {})))
5049

5150
(defn die* [msg]
52-
(die (str (.getStackTrace (Thread/currentThread)) "\n" msg)))
51+
(die msg))
5352

5453
(defn debug [msg]
5554
(warn (str ">>> " msg)))
@@ -68,7 +67,7 @@
6867
(defn name* [name func trace]
6968
(with-meta func {:trace (or trace name)}))
7069

71-
;; Timer (for performance measurement)
70+
;; Timer (for performance measurement - stubbed, tracing not used in Glojure)
7271
(defn timer
73-
([] (System/nanoTime))
74-
([start] (/ (- (System/nanoTime) start) 1000000000.0)))
72+
([] 0)
73+
([start] 0.0))

core/src/yamlstar/parser/receiver.clj

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
(ns yamlstar.parser.receiver
22
(:require [clojure.string :as str]
3-
[yamlstar.parser.prelude :refer :all]))
3+
[yamlstar.parser.prelude :refer :all]
4+
[yamlstar.parser.parser :as parser]))
45

56
;; Forward declarations
67
(declare push-event check-document-start check-document-end)
78

9+
;; Helper: convert hex string to Unicode character string
10+
(defn hex->char [hex-val]
11+
(let [[n _] (strconv.ParseInt hex-val 16 32)]
12+
(str (go/rune n))))
13+
814
;; Event constructors
915
(defn stream-start-event []
1016
{:event "stream_start"})
@@ -190,15 +196,17 @@
190196
;; hex escapes
191197
(re-matches (re-pattern (str "\\\\x(" hex "{2})")) match)
192198
(let [[_ hex-val] (re-matches (re-pattern (str "\\\\x(" hex "{2})")) match)]
193-
(str (char (Integer/parseInt hex-val 16))))
199+
(hex->char hex-val))
194200

195201
(re-matches (re-pattern (str "\\\\u(" hex "{4})")) match)
196202
(let [[_ hex-val] (re-matches (re-pattern (str "\\\\u(" hex "{4})")) match)]
197-
(str (char (Integer/parseInt hex-val 16))))
203+
(hex->char hex-val))
198204

199205
(re-matches (re-pattern (str "\\\\U(" hex "{8})")) match)
200206
(let [[_ hex-val] (re-matches (re-pattern (str "\\\\U(" hex "{8})")) match)]
201-
(String. (Character/toChars (Integer/parseInt hex-val 16))))
207+
;; go/rune handles all Unicode including above U+FFFF
208+
(let [[n _] (strconv.ParseInt hex-val 16 32)]
209+
(str (go/rune n))))
202210

203211
;; line continuation
204212
(re-matches #"(?:\\ ?\r?\n[ \t]*)" match)
@@ -440,9 +448,8 @@
440448
lines (map #(str (:text %) "\n") lines)
441449
text (apply str lines)
442450
;; :parser is stored directly (not as atom) in the receiver passed to callbacks
443-
parser (:parser receiver)
444-
state-curr @(requiring-resolve 'yamlstar.parser.parser/state-curr)
445-
t (:t (state-curr parser))
451+
p (:parser receiver)
452+
t (:t (parser/state-curr p))
446453
text (cond
447454
(= t "clip") (str/replace text #"\n+$" "\n")
448455
(= t "strip") (str/replace text #"\n+$" "")
@@ -485,15 +492,15 @@
485492
(reset! (:in-scalar receiver) false)
486493
(let [lines (map :text (cache-drop receiver))
487494
text (str/join "\n" lines)
495+
;; RE2 doesn't support lookaheads; capture and reinsert next char
488496
text (-> text
489-
(str/replace #"(?m)^(\S.*)\n(?=\S)" "$1 ")
497+
(str/replace #"(?m)^(\S.*)\n(\S)" "$1 $2")
490498
(str/replace #"(?m)^(\S.*)\n(\n+)" "$1$2")
491-
(str/replace #"(?m)^([ \t]+\S.*)\n(\n+)(?=\S)" "$1$2"))
499+
(str/replace #"(?m)^([ \t]+\S.*)\n(\n+)(\S)" "$1$2$3"))
492500
text (str text "\n")
493501
;; :parser is stored directly (not as atom) in the receiver passed to callbacks
494-
parser (:parser receiver)
495-
state-curr @(requiring-resolve 'yamlstar.parser.parser/state-curr)
496-
t (:t (state-curr parser))
502+
p (:parser receiver)
503+
t (:t (parser/state-curr p))
497504
text (cond
498505
(= t "clip") (let [t (str/replace text #"\n+$" "\n")]
499506
(if (= t "\n") "" t))
@@ -557,7 +564,7 @@
557564
;; URL-decode percent escapes
558565
resolved-tag (str/replace resolved-tag #"%([0-9a-fA-F]{2})"
559566
(fn [[_ hex]]
560-
(str (char (Integer/parseInt hex 16)))))]
567+
(hex->char hex)))]
561568
(reset! (:tag receiver) resolved-tag)))
562569

563570
;; Alias node

libyamlstar/src/libyamlstar.clj

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
(ns libyamlstar
2+
"Shared library bridge - Gloat EXPORT-based C API for YAMLStar"
3+
(:require [clojure.string :as str]
4+
[yamlstar.core :as yaml]
5+
[ys.json :as json]))
6+
7+
(def EXPORT
8+
{"yamlstar-load" [:str :str :str]
9+
"yamlstar-version" [:str]})
10+
11+
(defn nil-keys->string
12+
"Replace nil keys with string 'null' for JSON serialization.
13+
JSON allows null values but not null keys."
14+
[x]
15+
(cond
16+
(map? x) (into {} (map (fn [[k v]]
17+
[(if (nil? k) "null" (nil-keys->string k))
18+
(nil-keys->string v)])
19+
x))
20+
(vector? x) (mapv nil-keys->string x)
21+
(sequential? x) (map nil-keys->string x)
22+
:else x))
23+
24+
(defn yamlstar-load
25+
"Load YAML string, return JSON string with {:data ...} or {:error ...}"
26+
[yaml-str opts-json]
27+
(try
28+
(let [result (yaml/load yaml-str)]
29+
(json/dump {:data (nil-keys->string result)}))
30+
(catch go/any e
31+
(json/dump {:error {:cause (str e)
32+
:type "Exception"
33+
:message (str e)}}))))
34+
35+
(defn yamlstar-version
36+
"Return the YAMLStar version string"
37+
[]
38+
(yaml/version))

0 commit comments

Comments
 (0)