feat: jsonista-style production JSON example

- Добавени JSON runtime функции в cljnim_runtime.nim
  (write-value-as-string, read-value, file I/O, keyword-keys, pretty print)
- Регистрирани json/* функции в emitter.nim
- Създаден production пример examples/jsonista.clj
  (моделиран след metosin/jsonista, без Java зависимости)
- Добавен README examples/jsonista.md
- Поправен cljGetIn да приема vectors
- Поправен cljOptBool за ?-suffixed ключове
- Добавен пример в Makefile check
- Обновен .gitignore
This commit is contained in:
2026-05-11 10:54:29 +03:00
parent 456636700c
commit 55f0cd842c
6 changed files with 435 additions and 4 deletions
+139 -3
View File
@@ -1,6 +1,6 @@
import cljnim_pvec
import cljnim_pmap
import strutils, sequtils, hashes, algorithm, os, osproc, locks, math, random, re
import strutils, sequtils, hashes, algorithm, os, osproc, locks, math, random, re, json
type
CljKind* = enum
@@ -2006,11 +2006,12 @@ proc cljNotEq*(args: seq[CljVal]): CljVal =
cljNot(cljMultiEqual(args))
proc cljGetIn*(m: CljVal, keys: CljVal, default: CljVal = nil): CljVal =
if keys.isNil or keys.kind != ckList:
if keys.isNil or (keys.kind != ckList and keys.kind != ckVector):
if default != nil: return default
return cljNil()
var current = m
for key in keys.listItems:
let keyItems = if keys.kind == ckList: keys.listItems else: toSeq(keys.vecData)
for key in keyItems:
if current.isNil or current.kind != ckMap:
if default != nil: return default
return cljNil()
@@ -2402,3 +2403,138 @@ proc Float*(args: seq[CljVal]): CljVal =
proc Double*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljFloat(0.0)
cljToFloat(args)
# ---- JSON Support (jsonista-style) ----
proc cljOptBool(opts: CljVal, key: string, defaultVal: bool = false): bool =
if opts.isNil or opts.kind != ckMap: return defaultVal
let v = cljGet(opts, cljKeyword(key))
if v.isNil or v.kind == ckNil:
let v2 = cljGet(opts, cljKeyword(key & "?"))
if v2.isNil or v2.kind == ckNil: return defaultVal
if v2.kind == ckBool: return v2.boolVal
if v2.kind == ckInt: return v2.intVal != 0
return defaultVal
if v.kind == ckBool: return v.boolVal
if v.kind == ckInt: return v.intVal != 0
return defaultVal
proc cljToJsonNode*(v: CljVal, opts: CljVal = nil): JsonNode =
if v.isNil: return newJNull()
case v.kind
of ckNil: newJNull()
of ckBool: newJBool(v.boolVal)
of ckInt: newJInt(v.intVal)
of ckFloat: newJFloat(v.floatVal)
of ckString: newJString(v.strVal)
of ckKeyword: newJString(v.kwName)
of ckSymbol: newJString(v.symName)
of ckList:
var arr = newJArray()
for item in v.listItems:
arr.add(cljToJsonNode(item, opts))
arr
of ckVector:
var arr = newJArray()
for item in toSeq(v.vecData):
arr.add(cljToJsonNode(item, opts))
arr
of ckMap:
var obj = newJObject()
for key in pmapKeys(v.mapData):
let val = pmapGet(v.mapData, key, cljNil(), hash(key), cljEq)
var keyStr = ""
if key.kind == ckString: keyStr = key.strVal
elif key.kind == ckKeyword: keyStr = key.kwName
elif key.kind == ckSymbol: keyStr = key.symName
elif key.kind == ckInt: keyStr = $key.intVal
else: keyStr = cljStr(key)
obj[keyStr] = cljToJsonNode(val, opts)
obj
of ckSet:
var arr = newJArray()
for key in pmapKeys(v.setData):
arr.add(cljToJsonNode(key, opts))
arr
else: newJNull()
proc jsonNodeToClj*(j: JsonNode, opts: CljVal = nil): CljVal =
if j.isNil: return cljNil()
case j.kind
of JNull: cljNil()
of JBool: cljBool(j.getBool())
of JInt: cljInt(j.getInt())
of JFloat: cljFloat(j.getFloat())
of JString: cljString(j.getStr())
of JArray:
var items: seq[CljVal] = @[]
for elem in j.getElems():
items.add(jsonNodeToClj(elem, opts))
cljVector(items)
of JObject:
let keywordKeys = cljOptBool(opts, "keyword-keys")
var pairs: seq[(CljVal, CljVal)] = @[]
for key, val in j.pairs:
let k = if keywordKeys: cljKeyword(key) else: cljString(key)
pairs.add((k, jsonNodeToClj(val, opts)))
cljMapFromPairs(pairs)
else: cljNil()
proc cljJsonWriteString*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljString("")
let v = args[0]
var opts: CljVal = nil
if args.len >= 2 and args[1].kind == ckMap:
opts = args[1]
let j = cljToJsonNode(v, opts)
let pretty = cljOptBool(opts, "pretty")
if pretty:
cljString(pretty(j, 2))
else:
cljString($j)
proc cljJsonReadString*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
let s = args[0]
if s.kind != ckString: return cljNil()
var opts: CljVal = nil
if args.len >= 2 and args[1].kind == ckMap:
opts = args[1]
try:
let j = parseJson(s.strVal)
jsonNodeToClj(j, opts)
except CatchableError as e:
cljMapFromPairs(@[(cljKeyword("error"), cljString(e.msg))])
proc cljJsonWriteFile*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljMapFromPairs(@[(cljKeyword("error"), cljString("json/write-file requires at least path and value"))])
let path = args[0]
let v = args[1]
if path.kind != ckString:
return cljMapFromPairs(@[(cljKeyword("error"), cljString("json/write-file path must be a string"))])
var opts: CljVal = nil
if args.len >= 3 and args[2].kind == ckMap:
opts = args[2]
let j = cljToJsonNode(v, opts)
let pretty = cljOptBool(opts, "pretty")
try:
let content = if pretty: pretty(j, 2) else: $j
writeFile(path.strVal, content)
cljBool(true)
except CatchableError as e:
cljMapFromPairs(@[(cljKeyword("error"), cljString(e.msg))])
proc cljJsonReadFile*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljMapFromPairs(@[(cljKeyword("error"), cljString("json/read-file requires a path"))])
let path = args[0]
if path.kind != ckString:
return cljMapFromPairs(@[(cljKeyword("error"), cljString("json/read-file path must be a string"))])
var opts: CljVal = nil
if args.len >= 2 and args[1].kind == ckMap:
opts = args[1]
try:
let content = readFile(path.strVal)
let j = parseJson(content)
jsonNodeToClj(j, opts)
except CatchableError as e:
cljMapFromPairs(@[(cljKeyword("error"), cljString(e.msg))])