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:
+139
-3
@@ -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))])
|
||||
|
||||
Reference in New Issue
Block a user