Compare commits
2 Commits
2dd63903f0
...
99201de724
Author | SHA1 | Date |
---|---|---|
Emery Hemingway | 99201de724 | |
Emery Hemingway | 220577c8a0 |
|
@ -1,7 +1,9 @@
|
|||
tests/test_integers
|
||||
tests/test_parser
|
||||
tests/test_rfc8259
|
||||
tests/test_schemas
|
||||
preserves_encode
|
||||
preserves_decode
|
||||
preserves_from_json
|
||||
preserves_to_json
|
||||
preserves_schema_nim
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "upstream"]
|
||||
path = upstream
|
||||
url = https://gitlab.com/preserves/preserves.git
|
|
@ -6,7 +6,7 @@ description = "data model and serialization format"
|
|||
license = "Unlicense"
|
||||
srcDir = "src"
|
||||
|
||||
bin = @["preserves/private/preserves_schema_nim", "preserves/private/preserves_encode"]
|
||||
bin = @["preserves/preserves_schema_nim", "preserves/private/preserves_encode"]
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
|
|
@ -6,6 +6,14 @@ import std/[base64, endians, hashes, options, sets, sequtils, streams, tables, t
|
|||
|
||||
from std/json import escapeJson, escapeJsonUnquoted
|
||||
from std/macros import hasCustomPragma, getCustomPragmaVal
|
||||
import ./preserves/private/dot
|
||||
from std/strutils import parseEnum
|
||||
|
||||
when defined(tracePreserves):
|
||||
template trace(args: varargs[untyped]) =
|
||||
echo args
|
||||
else:
|
||||
template trace(args: varargs[untyped]) = discard
|
||||
|
||||
type
|
||||
PreserveKind* = enum
|
||||
|
@ -52,7 +60,6 @@ type
|
|||
|
||||
DictEntry[E] = tuple[key: Preserve[E], val: Preserve[E]]
|
||||
|
||||
|
||||
proc `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||||
## Check `x` and `y` for equivalence.
|
||||
if x.kind == y.kind and x.embedded == y.embedded:
|
||||
|
@ -207,6 +214,15 @@ proc `[]`*(pr, key: Preserve): Preserve =
|
|||
else:
|
||||
raise newException(ValueError, "`[]` is not valid for " & $pr.kind)
|
||||
|
||||
proc getOrDefault(pr: Preserve; key: Preserve): Preserve =
|
||||
## Retrieves the value of `pr[key]` if `pr` is a dictionary containing `key`
|
||||
## or returns the `#f` Preserves value.
|
||||
if pr.kind == pkDictionary:
|
||||
for (k, v) in pr.dict:
|
||||
if key == k:
|
||||
result = v
|
||||
break
|
||||
|
||||
proc incl*(pr: var Preserve; key: Preserve) =
|
||||
## Include `key` in the Preserves set `pr`.
|
||||
for i in 0..pr.set.high:
|
||||
|
@ -239,7 +255,7 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
|
|||
return
|
||||
pr.dict.add((key, val, ))
|
||||
|
||||
proc symbol*[E](s: string): Preserve[E] {.inline.} =
|
||||
proc toSymbol*(s: sink string; E = void): Preserve[E] {.inline.} =
|
||||
## Create a Preserves symbol value.
|
||||
Preserve[E](kind: pkSymbol, symbol: s)
|
||||
|
||||
|
@ -270,6 +286,30 @@ proc embed*[E](e: E): Preserve[E] =
|
|||
## Create a Preserves value that embeds ``e``.
|
||||
Preserve[E](kind: pkEmbedded, embed: e)
|
||||
|
||||
proc mapEmbeds*[A, B](pr: Preserve[A]; op: proc (v: A): B): Preserve[B] =
|
||||
## Convert `Preserve[A]` to `Preserve[B]` using an `A -> B` procedure.
|
||||
case pr.kind
|
||||
of pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol:
|
||||
result = cast[Preserve[B]](pr)
|
||||
of pkRecord:
|
||||
result = Preserve[B](kind: pr.kind)
|
||||
result.record = map(pr.record) do (x: Preserve[A]) -> Preserve[B]:
|
||||
mapEmbeds(x, op)
|
||||
of pkSequence:
|
||||
result = Preserve[B](kind: pr.kind)
|
||||
result.sequence = map(pr.sequence) do (x: Preserve[A]) -> Preserve[B]:
|
||||
mapEmbeds(x, op)
|
||||
of pkSet:
|
||||
result = Preserve[B](kind: pr.kind)
|
||||
result.set = map(pr.set) do (x: Preserve[A]) -> Preserve[B]:
|
||||
mapEmbeds(x, op)
|
||||
of pkDictionary:
|
||||
result = Preserve[B](kind: pr.kind)
|
||||
result.dict = map(pr.dict) do (e: DictEntry[A]) -> DictEntry[B]:
|
||||
(mapEmbeds(e.key, op), mapEmbeds(e.val, op))
|
||||
of pkEmbedded:
|
||||
result = embed op(pr.embed)
|
||||
|
||||
proc len*(pr: Preserve): int =
|
||||
## Return the shallow count of values in ``pr``, that is the number of
|
||||
## fields in a record, items in a sequence, items in a set, or key-value pairs
|
||||
|
@ -582,11 +622,37 @@ proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
|
|||
## Decode a byte-string of binary-encoded Preserves.
|
||||
decodePreserves(cast[string](s), E)
|
||||
|
||||
template record*(label: string) {.pragma.}
|
||||
## Serialize this object or tuple as a record. See ``toPreserve``.
|
||||
template preservesRecord*(label: string) {.pragma.}
|
||||
## Serialize this object or tuple as a record.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesTuple*() {.pragma.}
|
||||
## Serialize this object or tuple as a tuple.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesTupleTail*() {.pragma.}
|
||||
## Serialize this object field to the end of its containing tuple.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesDictionary*() {.pragma.}
|
||||
## Serialize this object or tuple as a dictionary.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesSymbol*() {.pragma.}
|
||||
## Serialize this string as a symbol.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesOr*() {.pragma.}
|
||||
## Serialize this object as an ``or`` alternative.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template preservesLiteral*(value: typed) {.pragma.}
|
||||
## Serialize a Preserves literal within this object.
|
||||
## See ``toPreserve``.
|
||||
|
||||
template unpreservable*() {.pragma.}
|
||||
## Pragma to forbid a type from being converted by ``toPreserve``.
|
||||
## See ``toPreserve``.
|
||||
|
||||
proc toPreserve*[T](x: T; E = void): Preserve[E] =
|
||||
## Serializes ``x`` to Preserves. Can be customized by defining
|
||||
|
@ -594,8 +660,13 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] =
|
|||
## Any ``toPreserveHook`` that does not compile will be discarded;
|
||||
## *Write tests for your hooks!*
|
||||
when (T is Preserve[E]): result = x
|
||||
elif T is E: result = embed(x)
|
||||
elif compiles(toPreserveHook(x, E)):
|
||||
result = toPreserveHook(x, E)
|
||||
elif T is distinct:
|
||||
result = toPreserve(x.distinctBase, E)
|
||||
elif T is enum:
|
||||
result = toSymbol($x, E)
|
||||
elif T is Bigint:
|
||||
result = Preserve[E](kind: pkBigInteger, bigint: x)
|
||||
elif T is seq[byte]:
|
||||
|
@ -605,33 +676,71 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] =
|
|||
for v in x.items: result.sequence.add(toPreserve(v, E))
|
||||
elif T is bool:
|
||||
result = Preserve[E](kind: pkBoolean, bool: x)
|
||||
elif T is distinct:
|
||||
result = toPreserve(x.distinctBase, E)
|
||||
elif T is float:
|
||||
result = Preserve[E](kind: pkFloat, float: x)
|
||||
elif T is float64:
|
||||
result = Preserve[E](kind: pkDouble, double: x)
|
||||
elif T is object | tuple:
|
||||
when T.hasCustomPragma(unpreservable): {.fatal: "unpreservable type".}
|
||||
elif T.hasCustomPragma(record):
|
||||
result = Preserve[E](kind: pkRecord)
|
||||
for _, f in x.fieldPairs: result.record.add(toPreserve(f, E))
|
||||
result.record.add(symbol[E](T.getCustomPragmaVal(record)))
|
||||
else:
|
||||
result = Preserve[E](kind: pkDictionary)
|
||||
for k, v in x.fieldPairs:
|
||||
result[symbol[E](k)] = toPreserve(v, E)
|
||||
elif T is tuple:
|
||||
result = Preserve[E](kind: pkSequence,
|
||||
sequence: newSeqOfCap[Preserve[E]](tupleLen(T)))
|
||||
for xf in fields(x):
|
||||
result.sequence.add(toPreserve(xf, E))
|
||||
elif T is Ordinal:
|
||||
result = Preserve[E](kind: pkSignedInteger, int: x.ord.BiggestInt)
|
||||
elif T is ptr | ref:
|
||||
if system.`==`(x, nil): result = symbol[E]("null")
|
||||
if system.`==`(x, nil): result = toSymbol("null", E)
|
||||
else: result = toPreserve(x[], E)
|
||||
elif T is string:
|
||||
result = Preserve[E](kind: pkString, string: x)
|
||||
elif T is SomeInteger:
|
||||
result = Preserve[E](kind: pkSignedInteger, int: x.BiggestInt)
|
||||
else:
|
||||
raiseAssert("unpreservable type" & $T)
|
||||
elif T is object:
|
||||
trace T, " is object"
|
||||
template fieldToPreserve(key: string; val: typed): Preserve =
|
||||
when x.dot(key).hasCustomPragma(preservesSymbol):
|
||||
toSymbol(val, E)
|
||||
elif x.dot(key).hasCustomPragma(preservesLiteral):
|
||||
const lit = parsePreserves x.dot(key).getCustomPragmaVal(preservesLiteral)
|
||||
lit
|
||||
else:
|
||||
toPreserve(val, E)
|
||||
when T.hasCustomPragma(unpreservable): {.fatal: "unpreservable type".}
|
||||
elif T.hasCustomPragma(preservesOr):
|
||||
var hasKind, hasVariant: bool
|
||||
for k, v in x.fieldPairs:
|
||||
trace T, ": iterate to ", k
|
||||
if k == "orKind":
|
||||
assert(not hasKind)
|
||||
hasKind = true
|
||||
else:
|
||||
assert(hasKind and not hasVariant)
|
||||
result = fieldToPreserve(k, v)
|
||||
hasVariant = true
|
||||
if not hasVariant:
|
||||
trace T, ": no value found"
|
||||
elif T.hasCustomPragma(preservesRecord):
|
||||
result = Preserve[E](kind: pkRecord)
|
||||
for k, v in x.fieldPairs:
|
||||
result.record.add(fieldToPreserve(k, v))
|
||||
result.record.add(tosymbol(T.getCustomPragmaVal(preservesRecord), E))
|
||||
elif T.hasCustomPragma(preservesTuple):
|
||||
result = initSequence[E]()
|
||||
for label, field in x.fieldPairs:
|
||||
when x.dot(label).hasCustomPragma(preservesTupleTail):
|
||||
for y in field.items:
|
||||
result.sequence.add(toPreserve(y, E))
|
||||
# TODO: what if there are fields after the tail?
|
||||
else:
|
||||
result.sequence.add(fieldToPreserve(label, field))
|
||||
elif T.hasCustomPragma(preservesDictionary):
|
||||
trace T, ": convert to a dictionary"
|
||||
result = initDictionary[E]()
|
||||
for key, val in x.fieldPairs:
|
||||
result[toSymbol(key, E)] = fieldToPreserve(key, val)
|
||||
else: result = toPreserveHook(x, E)
|
||||
else: result = toPreserveHook(x, E)
|
||||
# the hook doesn't compile but produces a useful error
|
||||
trace T, " -> ", result
|
||||
|
||||
proc toPreserveHook*[A](pr: Preserve[A]; E: typedesc): Preserve[E] =
|
||||
## Hook for converting ``Preserve`` values with different embedded types.
|
||||
|
@ -648,10 +757,21 @@ proc toPreserveHook*[T](set: HashSet[T]; E: typedesc): Preserve[E] =
|
|||
result = Preserve[E](kind: pkSet, set: newSeqOfCap[Preserve[E]](set.len))
|
||||
for e in set: result.incl toPreserve(e, E)
|
||||
|
||||
#[
|
||||
when isMainModule:
|
||||
var h: HashSet[string]
|
||||
var pr = h.toPreserveHook(void)
|
||||
assert fromPreserveHook(h, pr)
|
||||
]#
|
||||
|
||||
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B], E: typedesc): Preserve[E] =
|
||||
## Hook for preserving ``Table``.
|
||||
result = initDictionary[E]()
|
||||
for k, v in table.pairs: result[toPreserve(k, E)] = toPreserve(v, E)
|
||||
for k, v in table.pairs:
|
||||
when A is string:
|
||||
result[toSymbol(k, E)] = toPreserve(v, E)
|
||||
else:
|
||||
result[toPreserve(k, E)] = toPreserve(v, E)
|
||||
|
||||
proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
|
||||
## Inplace version of `preserveTo`. Returns ``true`` on
|
||||
|
@ -668,14 +788,23 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
|
|||
assert(fromPreserve(foo, parsePreserves("""<foo 1 2>""")))
|
||||
assert(foo.x == 1)
|
||||
assert(foo.y == 2)
|
||||
type Value = Preserve
|
||||
when T is Value:
|
||||
when T is Preserve[E]:
|
||||
v = pr
|
||||
result = true
|
||||
elif T is E:
|
||||
result = pr.embed
|
||||
if pr.kind == pkEmbedded:
|
||||
v = pr.embed
|
||||
result = true
|
||||
elif compiles(fromPreserveHook(v, pr)):
|
||||
result = fromPreserveHook(v, pr)
|
||||
elif T is distinct:
|
||||
result = fromPreserve(result.distinctBase, pr)
|
||||
elif T is enum:
|
||||
if pr.isSymbol:
|
||||
try:
|
||||
v = parseEnum[T](pr.symbol)
|
||||
result = true
|
||||
except ValueError: discard
|
||||
elif T is Bigint:
|
||||
case pr.kind
|
||||
of pkSignedInteger:
|
||||
|
@ -697,15 +826,19 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
|
|||
if pr.kind == pkFloat:
|
||||
v = pr.float
|
||||
result = true
|
||||
elif T is seq:
|
||||
if T is seq[byte] and pr.kind == pkByteString:
|
||||
elif T is seq[byte]:
|
||||
if pr.kind == pkByteString:
|
||||
v = pr.bytes
|
||||
result = true
|
||||
elif pr.kind == pkSequence:
|
||||
elif T is seq:
|
||||
if pr.kind == pkSequence:
|
||||
v.setLen(pr.len)
|
||||
result = true
|
||||
for i, e in pr.sequence:
|
||||
result = result and fromPreserve(v[i], e)
|
||||
if not result:
|
||||
v.setLen 0
|
||||
break
|
||||
elif T is float64:
|
||||
case pr.kind
|
||||
of pkFloat:
|
||||
|
@ -714,51 +847,112 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
|
|||
of pkDouble:
|
||||
v = pr.double
|
||||
result = true
|
||||
elif T is object | tuple:
|
||||
case pr.kind
|
||||
of pkRecord:
|
||||
when T.hasCustomPragma(record):
|
||||
if pr.record[pr.record.high].isSymbol T.getCustomPragmaVal(record):
|
||||
result = true
|
||||
var i = 0
|
||||
for fname, field in v.fieldPairs:
|
||||
if not result or (i == pr.record.high): break
|
||||
result = result and fromPreserve(field, pr.record[i])
|
||||
inc(i)
|
||||
result = result and (i == pr.record.high) # arity equivalence check=
|
||||
of pkDictionary:
|
||||
result = true
|
||||
var fieldCount = 0
|
||||
for key, val in v.fieldPairs:
|
||||
inc fieldCount
|
||||
for (pk, pv) in pr.dict.items:
|
||||
var sym = symbol[E](key)
|
||||
if sym == pk:
|
||||
result = result and fromPreserve(val, pv)
|
||||
break
|
||||
result = result and pr.dict.len == fieldCount
|
||||
else: discard
|
||||
elif T is Ordinal | SomeInteger:
|
||||
if pr.kind == pkSignedInteger:
|
||||
v = (T)pr.int
|
||||
result = true
|
||||
elif T is ref:
|
||||
if pr != symbol[E]("null"):
|
||||
new v
|
||||
result = fromPreserve(v[], pr)
|
||||
elif T is string:
|
||||
if pr.kind == pkString:
|
||||
case pr.kind
|
||||
of pkString:
|
||||
v = pr.string
|
||||
result = true
|
||||
elif T is distinct:
|
||||
result = fromPreserve(result.distinctBase, pr)
|
||||
of pkSymbol:
|
||||
v = pr.symbol
|
||||
result = true
|
||||
else: discard
|
||||
elif T is tuple:
|
||||
case pr.kind
|
||||
of pkRecord, pkSequence:
|
||||
if pr.len <= tupleLen(T):
|
||||
result = true
|
||||
var i: int
|
||||
for f in fields(v):
|
||||
if result and i < pr.len:
|
||||
result = result and fromPreserve(f, pr[i])
|
||||
inc i
|
||||
of pkDictionary:
|
||||
if tupleLen(T) == pr.len:
|
||||
result = true
|
||||
for key, val in fieldPairs(v):
|
||||
if result:
|
||||
result = result and fromPreserve(val, pr[toSymbol(key, E)])
|
||||
else: discard
|
||||
elif T is ref:
|
||||
if isNil(v): new(v)
|
||||
result = fromPreserve(v[], pr)
|
||||
elif T is object:
|
||||
template fieldFromPreserve(key: string; val: typed; pr: Preserve[E]): bool {.used.} =
|
||||
when v.dot(key).hasCustomPragma(preservesSymbol):
|
||||
if pr.isSymbol:
|
||||
fromPreserve(val, pr)
|
||||
else:
|
||||
false
|
||||
elif v.dot(key).hasCustomPragma(preservesLiteral):
|
||||
const lit = parsePreserves v.dot(key).getCustomPragmaVal(preservesLiteral)
|
||||
pr == lit
|
||||
else:
|
||||
fromPreserve(val, pr)
|
||||
when T.hasCustomPragma(preservesRecord):
|
||||
if pr.isRecord and pr.label.isSymbol(T.getCustomPragmaVal(preservesRecord)):
|
||||
result = true
|
||||
var i: int
|
||||
for key, val in fieldPairs(v):
|
||||
if result and i <= pr.len:
|
||||
result = result and fieldFromPreserve(key, val, pr.record[i])
|
||||
inc i
|
||||
result = result and (i == pr.len)
|
||||
elif T.hasCustomPragma(preservesTuple):
|
||||
if pr.isSequence:
|
||||
result = true
|
||||
var i: int
|
||||
for name, field in fieldPairs(v):
|
||||
when v.dot(name).hasCustomPragma(preservesTupleTail):
|
||||
setLen(v.dot(name), pr.len - i)
|
||||
var j: int
|
||||
while result and i < pr.len:
|
||||
result = result and fieldFromPreserve(name, v.dot(name)[j], pr.sequence[i])
|
||||
inc i
|
||||
inc j
|
||||
else:
|
||||
if result and i < pr.len:
|
||||
result = result and fromPreserve(field, pr.sequence[i])
|
||||
inc i
|
||||
result = result and (i == pr.len)
|
||||
elif T.hasCustomPragma(preservesDictionary):
|
||||
trace T, " is a preservesDictionary"
|
||||
if pr.isDictionary:
|
||||
result = true
|
||||
var i: int
|
||||
for key, _ in fieldPairs(v):
|
||||
let val = pr.getOrDefault(toSymbol(key, E))
|
||||
result = result and fieldFromPreserve(
|
||||
key, v.dot(key), val)
|
||||
if not result: break
|
||||
inc i
|
||||
result = result and (i == pr.len)
|
||||
elif T.hasCustomPragma(preservesOr):
|
||||
for kind in typeof(T.orKind):
|
||||
v = T(orKind: kind)
|
||||
var fieldWasFound = false
|
||||
for key, val in fieldPairs(v):
|
||||
when key != "orKind": # fieldPairs unwraps early
|
||||
result = fieldFromPreserve(key, v.dot(key), pr)
|
||||
fieldWasFound = true
|
||||
break
|
||||
if not fieldWasFound:
|
||||
# hopefully a `discard` of-branch, so discard `pr`
|
||||
result = true
|
||||
if result: break
|
||||
else: result = fromPreserveHook(v, pr)
|
||||
else: result = fromPreserveHook(v, pr)
|
||||
# the hook doesn't compile but produces a useful error
|
||||
if not result:
|
||||
trace T, " !- ", pr
|
||||
else:
|
||||
raiseAssert("no conversion of type Preserve to " & $T)
|
||||
if not result: reset v
|
||||
trace T, " <- ", pr
|
||||
|
||||
proc preserveTo*(pr: Preserve; T: typedesc): Option[T] =
|
||||
## Reverse of `toPreserve`.
|
||||
##
|
||||
# TODO: {.raises: [].}
|
||||
runnableExamples:
|
||||
import std/options, preserves, preserves/parse
|
||||
|
@ -788,11 +982,18 @@ proc fromPreserveHook*[A,B,E](t: var (Table[A,B]|TableRef[A,B]); pr: Preserve[E]
|
|||
result = true
|
||||
var a: A
|
||||
var b: B
|
||||
for (k, v) in pr.dict:
|
||||
for (k, v) in pr.dict.items:
|
||||
result = fromPreserve(a, k) and fromPreserve(b, v)
|
||||
if not result: break
|
||||
if not result:
|
||||
clear t
|
||||
break
|
||||
t[move a] = move b
|
||||
|
||||
when isMainModule:
|
||||
var t: Table[int, string]
|
||||
var pr = t.toPreserveHook(void)
|
||||
assert fromPreserveHook(t, pr)
|
||||
|
||||
proc concat[E](result: var string; pr: Preserve[E]) =
|
||||
if pr.embedded: result.add("#!")
|
||||
case pr.kind:
|
||||
|
@ -854,6 +1055,8 @@ proc concat[E](result: var string; pr: Preserve[E]) =
|
|||
result.concat(key)
|
||||
result.add(": ")
|
||||
result.concat(value)
|
||||
if i < pr.dict.high:
|
||||
result.add(',')
|
||||
inc i
|
||||
result.add('}')
|
||||
of pkEmbedded:
|
||||
|
@ -863,13 +1066,3 @@ proc concat[E](result: var string; pr: Preserve[E]) =
|
|||
|
||||
proc `$`*(pr: Preserve): string = concat(result, pr)
|
||||
## Generate the textual representation of ``pr``.
|
||||
|
||||
when isMainModule:
|
||||
block:
|
||||
var t: Table[int, string]
|
||||
var pr = t.toPreserveHook(void)
|
||||
assert fromPreserveHook(t, pr)
|
||||
block:
|
||||
var h: HashSet[string]
|
||||
var pr = h.toPreserveHook(void)
|
||||
assert fromPreserveHook(h, pr)
|
||||
|
|
|
@ -14,10 +14,10 @@ proc toPreserveHook*(js: JsonNode; E: typedesc): Preserve[E] =
|
|||
result = Preserve[E](kind: pkDouble, double: js.fnum)
|
||||
of JBool:
|
||||
result = case js.bval
|
||||
of false: symbol[E]"false"
|
||||
of true: symbol[E]"true"
|
||||
of false: toSymbol("false", E)
|
||||
of true: toSymbol("true", E)
|
||||
of JNull:
|
||||
result = symbol[E]"null"
|
||||
result = toSymbol("null", E)
|
||||
of JObject:
|
||||
result = Preserve[E](kind: pkDictionary)
|
||||
for key, val in js.fields.pairs:
|
||||
|
|
|
@ -0,0 +1,796 @@
|
|||
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
## This module implements Nim code generation from Preserves schemas.
|
||||
# This module imports code that it generates! After making any changes here
|
||||
# the schema module must be regenerated!
|
||||
# nim c -r ./preserves_schema_nim ../../upstream/schema/schema.bin
|
||||
|
||||
import std/[hashes, sequtils, strutils, sets, tables]
|
||||
|
||||
import compiler/[ast, idents, renderer, lineinfos]
|
||||
|
||||
import ../preserves, ./schema, ./parse
|
||||
|
||||
type
|
||||
TypeSpec = tuple[node: PNode, embeddable: bool]
|
||||
TypeTable = OrderedTable[string, PNode]
|
||||
|
||||
proc add(parent, child: PNode): PNode {.discardable.} =
|
||||
parent.sons.add child
|
||||
parent
|
||||
|
||||
proc add(parent: PNode; children: varargs[PNode]): PNode {.discardable.} =
|
||||
parent.sons.add children
|
||||
parent
|
||||
|
||||
proc nn(kind: TNodeKind; children: varargs[PNode]): PNode =
|
||||
result = newNode(kind)
|
||||
result.sons.add(children)
|
||||
|
||||
proc nn(kind: TNodeKind; child: PNode): PNode =
|
||||
result = newNode(kind)
|
||||
result.sons.add(child)
|
||||
|
||||
proc ident(s: string): PNode =
|
||||
newIdentNode(PIdent(s: s), TLineInfo())
|
||||
|
||||
proc accQuote(n: PNode): Pnode =
|
||||
nkAccQuoted.newNode.add(n)
|
||||
|
||||
proc pattern(np: NamedPattern): Pattern =
|
||||
case np.orKind
|
||||
of NamedPatternKind.named:
|
||||
Pattern(orKind: PatternKind.SimplePattern, simplePattern: np.named.pattern)
|
||||
of NamedPatternKind.anonymous:
|
||||
np.anonymous
|
||||
|
||||
proc pattern(np: NamedSimplePattern): SimplePattern =
|
||||
case np.orKind
|
||||
of NamedSimplePatternKind.named:
|
||||
np.named.pattern
|
||||
of NamedSimplePatternKind.anonymous:
|
||||
np.anonymous
|
||||
|
||||
proc ident(sp: SimplePattern): PNode =
|
||||
raiseAssert "need ident from " & $sp
|
||||
|
||||
proc ident(cp: CompoundPattern; fallback: string): PNode =
|
||||
case cp.orKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
ident($cp.rec.label)
|
||||
of CompoundPatternKind.`tuple`,
|
||||
CompoundPatternKind.`tuplePrefix`,
|
||||
CompoundPatternKind.`dict`:
|
||||
ident(fallback)
|
||||
|
||||
proc ident(pat: Pattern; fallback = string): PNode =
|
||||
case pat.orKind
|
||||
of PatternKind.simplePattern:
|
||||
ident(pat.simplePattern, fallback)
|
||||
of PatternKind.compoundPattern:
|
||||
ident(pat.compoundPattern, fallback)
|
||||
|
||||
proc ident(np: NamedPattern; fallback: string): PNode =
|
||||
case np.orKind
|
||||
of NamedPatternKind.`named`:
|
||||
ident(np.named.name)
|
||||
of NamedPatternKind.`anonymous`:
|
||||
ident(fallback)
|
||||
|
||||
proc ident(np: NamedSimplePattern; fallback: string): PNode =
|
||||
case np.orKind
|
||||
of NamedSimplePatternKind.`named`:
|
||||
ident(np.named.name)
|
||||
of NamedSimplePatternKind.`anonymous`:
|
||||
ident(fallback)
|
||||
|
||||
proc parameterize(node: PNode; embeddable: bool): PNode =
|
||||
if embeddable and node.kind notin {nkBracketExpr}:
|
||||
nn(nkBracketExpr, node, ident"E")
|
||||
else: node
|
||||
|
||||
proc parameterize(spec: TypeSpec): PNode =
|
||||
parameterize(spec.node, spec.embeddable)
|
||||
|
||||
proc isPreserve(n: PNode): bool =
|
||||
n.kind == nkBracketExpr and
|
||||
n.renderTree == "Preserve[E]"
|
||||
|
||||
proc orEmbed(x: var TypeSpec; y: TypeSpec) =
|
||||
x.embeddable = x.embeddable or y.embeddable
|
||||
|
||||
proc dotExtend(result: var PNode; label: string) =
|
||||
var id = ident(label)
|
||||
if result.isNil: result = id
|
||||
else: result = nn(nkDotExpr, result, id)
|
||||
|
||||
proc ident(`ref`: Ref): PNode =
|
||||
for m in`ref`.module: dotExtend(result, m)
|
||||
dotExtend(result, `ref`.name.capitalizeAscii)
|
||||
|
||||
proc deref(scm: Schema; r: Ref): Definition =
|
||||
assert r.module == @[]
|
||||
scm.data.definitions[r.name]
|
||||
|
||||
proc isEmbeddable(scm: Schema): bool =
|
||||
scm.data.embeddedType.orKind == EmbeddedtypenameKind.`Ref`
|
||||
|
||||
proc preserveIdent(scm: Schema): Pnode =
|
||||
nn(nkBracketExpr, ident"Preserve", ident(if scm.isEmbeddable: "E" else: "void"))
|
||||
|
||||
proc embeddedIdent(scm: Schema): PNode =
|
||||
case scm.data.embeddedType.orKind
|
||||
of EmbeddedtypenameKind.`false`: ident"void"
|
||||
of EmbeddedtypenameKind.`Ref`: preserveIdent(scm)
|
||||
|
||||
proc hash(r: Ref): Hash = r.toPreserve.hash
|
||||
type RefSet = HashSet[Ref]
|
||||
|
||||
proc isEmbeddable(scm: Schema; pat: Pattern; seen: RefSet): bool {.gcsafe.}
|
||||
proc isEmbeddable(scm: Schema; def: Definition; seen: RefSet): bool {.gcsafe.}
|
||||
|
||||
proc isEmbeddable(scm: Schema; sp: SimplePattern; seen: RefSet): bool =
|
||||
if not scm.isEmbeddable: false
|
||||
else:
|
||||
case sp.orKind
|
||||
of SimplepatternKind.`atom`, SimplepatternKind.`lit`: false
|
||||
of SimplepatternKind.`any`: true
|
||||
of SimplepatternKind.`embedded`: true
|
||||
of SimplepatternKind.`seqof`:
|
||||
isEmbeddable(scm, sp.seqof.pattern, seen)
|
||||
of SimplepatternKind.`setof`:
|
||||
isEmbeddable(scm, sp.setof.pattern, seen)
|
||||
of SimplepatternKind.`dictof`:
|
||||
isEmbeddable(scm, sp.dictof.key, seen) or
|
||||
isEmbeddable(scm, sp.dictof.value, seen)
|
||||
of SimplepatternKind.`Ref`:
|
||||
if sp.ref.module != @[]: true
|
||||
else:
|
||||
if sp.ref in seen: false
|
||||
else:
|
||||
var seen = seen
|
||||
seen.incl sp.ref
|
||||
isEmbeddable(scm, deref(scm, sp.ref), seen)
|
||||
|
||||
proc isEmbeddable(scm: Schema; np: NamedSimplePattern; seen: RefSet): bool =
|
||||
case np.orKind
|
||||
of NamedSimplePatternKind.`named`:
|
||||
isEmbeddable(scm, np.named.pattern, seen)
|
||||
of NamedSimplePatternKind.`anonymous`:
|
||||
isEmbeddable(scm, np.anonymous, seen)
|
||||
|
||||
proc isEmbeddable(scm: Schema; cp: CompoundPattern; seen: RefSet): bool =
|
||||
if not scm.isEmbeddable: false
|
||||
else:
|
||||
case cp.orKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
isEmbeddable(scm, cp.rec.label.pattern, seen) or
|
||||
isEmbeddable(scm, cp.rec.fields.pattern, seen)
|
||||
of CompoundPatternKind.`tuple`:
|
||||
any(cp.tuple.patterns) do (np: NamedPattern) -> bool:
|
||||
isEmbeddable(scm, np.pattern, seen)
|
||||
of CompoundPatternKind.`tupleprefix`:
|
||||
proc pred(np: NamedPattern): bool =
|
||||
isEmbeddable(scm, np.pattern, seen)
|
||||
isEmbeddable(scm, cp.tupleprefix.variable, seen) or
|
||||
any(cp.tupleprefix.fixed, pred)
|
||||
of CompoundPatternKind.`dict`:
|
||||
true # the key type is `Preserve`
|
||||
|
||||
proc isEmbeddable(scm: Schema; pat: Pattern; seen: RefSet): bool =
|
||||
case pat.orKind
|
||||
of PatternKind.`SimplePattern`:
|
||||
isEmbeddable(scm, pat.simplePattern, seen)
|
||||
of PatternKind.`CompoundPattern`:
|
||||
isEmbeddable(scm, pat.compoundPattern, seen)
|
||||
|
||||
proc isEmbeddable(scm: Schema; def: Definition; seen: RefSet): bool =
|
||||
if not scm.isEmbeddable: false
|
||||
else:
|
||||
case def.orKind
|
||||
of DefinitionKind.`or`:
|
||||
proc isEmbeddable(na: NamedAlternative): bool =
|
||||
isEmbeddable(scm, na.pattern, seen)
|
||||
isEmbeddable(def.or.data.pattern0) or
|
||||
isEmbeddable(def.or.data.pattern1) or
|
||||
any(def.or.data.patternN, isEmbeddable)
|
||||
of DefinitionKind.`and`:
|
||||
proc isEmbeddable(np: NamedPattern): bool =
|
||||
isEmbeddable(scm, np.pattern, seen)
|
||||
isEmbeddable(def.and.data.pattern0) or
|
||||
isEmbeddable(def.and.data.pattern1) or
|
||||
any(def.and.data.patternN, isEmbeddable)
|
||||
of DefinitionKind.`Pattern`:
|
||||
isEmbeddable(scm, def.pattern, seen)
|
||||
|
||||
proc isEmbeddable(scm: Schema; p: Definition|Pattern|SimplePattern): bool =
|
||||
var seen: RefSet
|
||||
isEmbeddable(scm, p, seen)
|
||||
|
||||
proc isLiteral(scm: Schema; def: Definition): bool {.gcsafe.}
|
||||
|
||||
proc isLiteral(scm: Schema; sp: SimplePattern): bool =
|
||||
case sp.orKind
|
||||
of SimplepatternKind.`Ref`:
|
||||
if sp.ref.module.len == 0:
|
||||
result = isLiteral(scm, deref(scm, sp.ref))
|
||||
of SimplepatternKind.lit:
|
||||
result = true
|
||||
else: discard
|
||||
|
||||
proc isLiteral(scm: Schema; pat: Pattern): bool =
|
||||
case pat.orKind
|
||||
of PatternKind.SimplePattern:
|
||||
isLiteral(scm, pat.simplePattern)
|
||||
of PatternKind.CompoundPattern:
|
||||
false # TODO it could be a compound of all literals
|
||||
|
||||
proc isLiteral(scm: Schema; def: Definition): bool =
|
||||
if def.orKind == DefinitionKind.Pattern:
|
||||
result = isLiteral(scm, def.pattern)
|
||||
|
||||
proc isRef(sp: SimplePattern): bool =
|
||||
sp.orKind == SimplePatternKind.`Ref`
|
||||
|
||||
proc isRef(pat: Pattern): bool =
|
||||
pat.orKind == PatternKind.SimplePattern and
|
||||
pat.simplePattern.isRef
|
||||
|
||||
proc isSimple(pat: Pattern): bool =
|
||||
pat.orKind == PatternKind.SimplePattern
|
||||
|
||||
proc isSymbolEnum(scm: Schema; orDef: DefinitionOr): bool =
|
||||
proc isLiteral(na: NamedAlternative): bool = isLiteral(scm, na.pattern)
|
||||
result = isLiteral(orDef.data.pattern0) and isLiteral(orDef.data.pattern1)
|
||||
for na in orDef.data.patternN:
|
||||
if not result: break
|
||||
result = isLiteral(na)
|
||||
|
||||
proc isSymbolEnum(scm: Schema; def: Definition): bool =
|
||||
case def.orKind
|
||||
of DefinitionKind.Pattern:
|
||||
if def.pattern.orKind == PatternKind.SimplePattern and
|
||||
def.pattern.simplePattern.orKind == SimplepatternKind.`Ref` and
|
||||
def.pattern.simplePattern.ref.module.len == 0:
|
||||
result = isSymbolEnum(scm, deref(scm, def.pattern.simplePattern.ref))
|
||||
# TODO: no need to de-ref this
|
||||
of DefinitionKind.or:
|
||||
result = isSymbolEnum(scm, def.or)
|
||||
else: discard
|
||||
|
||||
proc typeIdent(atom: AtomKind): PNode =
|
||||
case atom
|
||||
of AtomKind.`Boolean`: ident"bool"
|
||||
of AtomKind.`Float`: ident"float32"
|
||||
of AtomKind.`Double`: ident"float64"
|
||||
of AtomKind.`Signedinteger`: ident"BiggestInt"
|
||||
of AtomKind.`String`: ident"string"
|
||||
of AtomKind.`Bytestring`: nn(nkBracketExpr, ident"seq", ident"byte")
|
||||
of AtomKind.`Symbol`: ident"string"
|
||||
|
||||
proc typeIdent(scm: Schema; sp: SimplePattern): TypeSpec =
|
||||
case sp.orKind
|
||||
of SimplepatternKind.`atom`:
|
||||
result = (typeIdent(sp.atom.atomKind), false)
|
||||
of SimplepatternKind.`embedded`:
|
||||
result = (scm.embeddedIdent, scm.isEmbeddable)
|
||||
of SimplepatternKind.`seqof`:
|
||||
result = typeIdent(scm, sp.seqof.pattern)
|
||||
result.node = nn(nkBracketExpr, ident"seq", result.node)
|
||||
of SimplepatternKind.`setof`:
|
||||
result = typeIdent(scm, sp.setof.pattern)
|
||||
result.node = nn(nkBracketExpr, ident"HashSet", result.node)
|
||||
of SimplepatternKind.`dictof`:
|
||||
let
|
||||
key = typeIdent(scm, sp.dictof.key)
|
||||
val = typeIdent(scm, sp.dictof.value)
|
||||
result.node = nn(nkBracketExpr, ident"Table", key.node, val.node)
|
||||
result.embeddable = key.embeddable or val.embeddable
|
||||
of SimplepatternKind.`Ref`:
|
||||
result = (ident(sp.ref), isEmbeddable(scm, sp))
|
||||
result.node = parameterize(result)
|
||||
else:
|
||||
result = (preserveIdent(scm), isEmbeddable(scm))
|
||||
|
||||
proc typeIdent(scm: Schema; pat: Pattern): TypeSpec =
|
||||
case pat.orKind
|
||||
of PatternKind.SimplePattern: typeIdent(scm, pat.simplePattern)
|
||||
else: raiseAssert "no typeIdent for " & $pat
|
||||
|
||||
proc toExport(n: sink PNode): PNode =
|
||||
nkPostFix.newNode.add(ident"*", n)
|
||||
|
||||
proc toStrLit(scm: Schema; sp: SimplePattern): PNode =
|
||||
case sp.orKind
|
||||
of SimplePatternKind.`lit`:
|
||||
result = PNode(kind: nkStrLit, strVal: $sp.lit.value)
|
||||
of SimplePatternKind.`Ref`:
|
||||
let def = deref(scm, sp.ref)
|
||||
result = toStrLit(scm, def.pattern.simplePattern)
|
||||
else: assert false
|
||||
|
||||
proc toFieldIdent(s: string): PNode =
|
||||
nn(nkPostFix, ident("*"), nn(nkAccQuoted, ident(s)))
|
||||
|
||||
proc toFieldIdent(scm: Schema, label: string; pat: Pattern): PNode =
|
||||
result = label.toFieldIdent
|
||||
if isLiteral(scm, pat):
|
||||
result = nn(nkPragmaExpr,
|
||||
result,
|
||||
nn(nkPragma,
|
||||
nn(nkExprColonExpr,
|
||||
ident"preservesLiteral",
|
||||
toStrLit(scm, pat.simplePattern))))
|
||||
|
||||
proc newEmpty(): PNode = newNode(nkEmpty)
|
||||
|
||||
#[
|
||||
proc literal(scm: Schema; sn: SchemaNode): Value =
|
||||
case sn.orKind
|
||||
of snkLiteral: result = sn.value
|
||||
of snkRef:
|
||||
if sn.refPath.len == 1:
|
||||
result = literal(scm, scm.definitions[sn.refPath[0]])
|
||||
else:
|
||||
raiseAssert("not convertable to a literal: " & $sn)
|
||||
else:
|
||||
raiseAssert("not convertable to a literal: " & $sn)
|
||||
]#
|
||||
|
||||
proc embeddingParams(embeddable: bool): PNode =
|
||||
if embeddable:
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void"))
|
||||
else:
|
||||
newEmpty()
|
||||
|
||||
proc identDef(a, b: PNode; embeddable: bool): PNode =
|
||||
if embeddable and b.kind notin {nkBracketExpr, nkTupleTy}:
|
||||
# TODO: probably not a sufficient check
|
||||
nn(nkIdentDefs, a, nn(nkBracketExpr, b, ident"E"), newEmpty())
|
||||
else:
|
||||
nn(nkIdentDefs, a, b, newEmpty())
|
||||
|
||||
proc identDef(l: PNode; ts: TypeSpec): PNode =
|
||||
identDef(l, ts.node, ts.embeddable)
|
||||
|
||||
proc label(pat: Pattern): string =
|
||||
raiseAssert "need to derive record label for " & $pat
|
||||
|
||||
proc label(na: NamedPattern): string =
|
||||
case na.orKind
|
||||
of NamedPatternKind.`named`:
|
||||
na.named.name
|
||||
of NamedPatternKind.`anonymous`:
|
||||
"data" # TODO
|
||||
|
||||
proc idStr(sp: SimplePattern): string =
|
||||
if sp.orKind == SimplepatternKind.lit:
|
||||
case sp.lit.value.kind
|
||||
of pkString:
|
||||
result = sp.lit.value.string
|
||||
of pkSymbol:
|
||||
result = sp.lit.value.symbol
|
||||
else: discard
|
||||
doAssert(result != "", "no idStr for " & $sp)
|
||||
|
||||
proc idStr(pat: Pattern): string =
|
||||
doAssert(pat.orKind == PatternKind.SimplePattern)
|
||||
pat.simplePattern.idStr
|
||||
|
||||
proc idStr(np: NamedPattern): string =
|
||||
case np.orKind
|
||||
of NamedPatternKind.`named`:
|
||||
np.named.name
|
||||
of NamedPatternKind.`anonymous`:
|
||||
np.anonymous.idStr
|
||||
|
||||
proc typeDef(scm: Schema; name: string; pat: Pattern; ty: PNode): PNode =
|
||||
let
|
||||
embedParams = embeddingParams(isEmbeddable(scm, pat))
|
||||
id = name.ident.toExport
|
||||
if pat.orKind == PatternKind.`CompoundPattern`:
|
||||
case pat.compoundPattern.orKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
id, nn(nkPragma,
|
||||
nn(nkExprColonExpr,
|
||||
ident"preservesRecord",
|
||||
PNode(kind: nkStrLit, strVal: pat.compoundPattern.rec.label.idStr)))),
|
||||
embedParams,
|
||||
ty)
|
||||
of CompoundPatternKind.`tuple`, CompoundPatternKind.`tuplePrefix`:
|
||||
nn(nkTypeDef,
|
||||
nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesTuple")),
|
||||
embedParams,
|
||||
ty)
|
||||
of CompoundPatternKind.`dict`:
|
||||
nn(nkTypeDef,
|
||||
nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesDictionary")),
|
||||
embedParams,
|
||||
ty)
|
||||
else:
|
||||
nn(nkTypeDef, name.ident.toExport, embedParams, ty)
|
||||
|
||||
proc typeDef(scm: Schema; name: string; def: Definition; ty: PNode): PNode =
|
||||
case def.orKind
|
||||
of DefinitionKind.or:
|
||||
nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
name.ident.accQuote.toExport,
|
||||
nn(nkPragma, ident"preservesOr")),
|
||||
embeddingParams(isEmbeddable(scm, def)),
|
||||
ty)
|
||||
of DefinitionKind.and:
|
||||
raiseAssert "And variants not suported"
|
||||
of DefinitionKind.Pattern:
|
||||
typeDef(scm, name, def.pattern, ty)
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; nsp: NamedSimplePattern; name = ""): TypeSpec
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; pat: Pattern; name = ""): TypeSpec
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; cp: CompoundPattern; name = ""): TypeSpec
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; sp: SimplePattern; name = ""): TypeSpec =
|
||||
typeIdent(scm, sp)
|
||||
|
||||
proc addField(recList: PNode; scm: Schema; known: var TypeTable; sp: SimplePattern; label: string): PNode {.discardable.} =
|
||||
let id = label.toFieldIdent
|
||||
if isLiteral(scm, sp):
|
||||
let id = nn(nkPragmaExpr,
|
||||
id,
|
||||
nn(nkPragma,
|
||||
nn(nkExprColonExpr,
|
||||
ident"preservesLiteral",
|
||||
toStrLit(scm, sp))))
|
||||
recList.add identDef(id, (ident"bool", false))
|
||||
elif sp.orKind == SimplePatternKind.`atom` and
|
||||
sp.atom.atomKind == AtomKind.Symbol:
|
||||
let id = nn(nkPragmaExpr,
|
||||
id, nn(nkPragma, ident"preservesSymbol"))
|
||||
recList.add identDef(id, (ident"string", false))
|
||||
else:
|
||||
recList.add identDef(id, nimTypeOf(scm, known, sp))
|
||||
|
||||
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; cp: CompoundPattern; parentName: string): PNode {.discardable.} =
|
||||
case cp.orKind
|
||||
of CompoundPatternKind.rec:
|
||||
# recList.add identDef(ident(label), nimTypeOf(scm, known, cp, ""))
|
||||
raiseassert "unexpected record of fields " & $cp.rec
|
||||
of CompoundPatternKind.tuple:
|
||||
for np in cp.tuple.patterns:
|
||||
let
|
||||
label = np.label
|
||||
id = label.toFieldIdent
|
||||
if np.pattern.isRef or np.pattern.isSimple:
|
||||
addField(recList, scm, known, np.pattern.simplePattern, label)
|
||||
else:
|
||||
var
|
||||
pat = np.pattern
|
||||
typeName = parentName & capitalizeAscii(label)
|
||||
fieldSpec = nimTypeOf(scm, known, pat, label)
|
||||
known[typeName] = typeDef(scm, typeName, pat, fieldSpec.node)
|
||||
recList.add identDef(id, ident(typeName), fieldSpec.embeddable)
|
||||
else: raiseAssert "not adding fields for " & $cp
|
||||
reclist
|
||||
|
||||
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; pat: Pattern; parentName: string): PNode {.discardable.} =
|
||||
case pat.orKind
|
||||
of PatternKind.SimplePattern:
|
||||
raiseAssert "addFields called with SimplePattern " & $pat.simplePattern
|
||||
# addField(recList, scm, known, pat.simplePattern, "data")
|
||||
of PatternKind.CompoundPattern:
|
||||
discard addFields(recList, scm, known, pat.compoundPattern, parentName)
|
||||
reclist
|
||||
|
||||
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} =
|
||||
for key, val in entries.pairs:
|
||||
doAssert(key.isSymbol)
|
||||
let label = key.symbol
|
||||
addField(recList, scm, known, val.pattern, label)
|
||||
recList
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; nsp: NamedSimplePattern; name: string): TypeSpec =
|
||||
case nsp.orKind
|
||||
of NamedsimplepatternKind.named:
|
||||
nimTypeOf(scm, known, nsp.named.pattern, nsp.named.name)
|
||||
of NamedsimplepatternKind.anonymous:
|
||||
nimTypeOf(scm, known, nsp.anonymous, name)
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; cp: CompoundPattern; name: string): TypeSpec =
|
||||
case cp.orKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
result.node = nn(nkObjectTy,
|
||||
newEmpty(), newEmpty(),
|
||||
nn(nkRecList).addFields(scm, known, cp.rec.fields.pattern, name))
|
||||
of CompoundPatternKind.`tuple`:
|
||||
var recList = nn(nkRecList)
|
||||
for np in cp.tuple.patterns:
|
||||
let pat = np.pattern
|
||||
if not isLiteral(scm, pat):
|
||||
let fieldType = nimTypeOf(scm, known, pat)
|
||||
orEmbed result, fieldType
|
||||
recList.add identDef(ident(np, name).accQuote.toExport, fieldType)
|
||||
result.node = nn(nkObjectTy, newEmpty(), newEmpty(), recList)
|
||||
of CompoundPatternKind.`tupleprefix`:
|
||||
var recList = nn(nkRecList)
|
||||
for np in cp.tuplePrefix.fixed:
|
||||
let pat = np.pattern
|
||||
if not isLiteral(scm, pat):
|
||||
let fieldType = nimTypeOf(scm, known, pat)
|
||||
orEmbed result, fieldType
|
||||
recList.add identDef(ident(np, name).accQuote.toExport, fieldType)
|
||||
let variableType = nimTypeOf(scm, known, cp.tuplePrefix.variable)
|
||||
recList.add identDef(
|
||||
nn(nkPragmaExpr,
|
||||
ident(cp.tuplePrefix.variable, name).accQuote.toExport,
|
||||
nn(nkPragma, ident"preservesTupleTail")),
|
||||
variableType.parameterize,
|
||||
variableType.embeddable)
|
||||
result.node = nn(nkObjectTy, newEmpty(), newEmpty(), recList)
|
||||
of CompoundPatternKind.`dict`:
|
||||
result.node = nn(nkObjectTy, newEmpty(), newEmpty(),
|
||||
nn(nkRecList).addFields(scm, known, cp.dict.entries, name))
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; pat: Pattern; name: string): TypeSpec =
|
||||
case pat.orKind
|
||||
of PatternKind.SimplePattern:
|
||||
nimTypeOf(scm, known, pat.simplePattern, name)
|
||||
of PatternKind.CompoundPattern:
|
||||
nimTypeOf(scm, known, pat.compoundPattern, name)
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; orDef: DefinitionOr; name: string): TypeSpec =
|
||||
proc toEnumTy(): PNode =
|
||||
let ty = nkEnumTy.newNode.add newEmpty()
|
||||
proc add (na: NamedAlternative) =
|
||||
ty.add na.variantLabel.ident.accQuote
|
||||
add(orDef.data.pattern0)
|
||||
add(orDef.data.pattern1)
|
||||
for na in orDef.data.patternN:
|
||||
add(na)
|
||||
ty
|
||||
if isSymbolEnum(scm, orDef):
|
||||
result.node = toEnumTy()
|
||||
else:
|
||||
let
|
||||
enumName = name & "Kind"
|
||||
enumIdent = ident(enumName)
|
||||
if enumName notin known:
|
||||
known[enumName] = nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
enumName.ident.toExport,
|
||||
nn(nkPragma, ident"pure")),
|
||||
newEmpty(),
|
||||
toEnumTy())
|
||||
let recCase = nkRecCase.newNode.add(
|
||||
nkIdentDefs.newNode.add(
|
||||
"orKind".ident.toExport,
|
||||
enumName.ident,
|
||||
newEmpty()))
|
||||
template addCase(na: NamedAlternative) =
|
||||
let branchRecList = nn(nkRecList)
|
||||
var memberType: TypeSpec
|
||||
if isLiteral(scm, na.pattern):
|
||||
memberType.node = ident"bool"
|
||||
elif na.pattern.isRef:
|
||||
memberType = typeIdent(scm, na.pattern)
|
||||
else:
|
||||
let memberTypeName = name & na.variantLabel.capitalizeAscii
|
||||
memberType.node = ident memberTypeName
|
||||
let ty = nimTypeOf(scm, known, na.pattern, memberTypeName)
|
||||
orEmbed memberType, ty
|
||||
if memberTypeName notin known and not isLiteral(scm, na.pattern):
|
||||
known[memberTypeName] =
|
||||
typeDef(scm, memberTypeName, na.pattern, ty.node)
|
||||
orEmbed result, memberType
|
||||
branchRecList.add nn(nkIdentDefs,
|
||||
toFieldIdent(scm, na.variantLabel.normalize, na.pattern),
|
||||
memberType.node, newEmpty())
|
||||
recCase.add nn(nkOfBranch,
|
||||
nn(nkDotExpr,
|
||||
enumIdent, na.variantLabel.ident.accQuote),
|
||||
branchRecList)
|
||||
addCase(orDef.data.pattern0)
|
||||
addCase(orDef.data.pattern1)
|
||||
for na in orDef.data.patternN: addCase(na)
|
||||
result.node = nn(nkRefTy, nn(nkObjectTy,
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkRecList, recCase)))
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; def: Definition; name: string): TypeSpec =
|
||||
case def.orKind
|
||||
of DefinitionKind.or:
|
||||
nimTypeOf(scm, known, def.or, name)
|
||||
of DefinitionKind.and: raiseAssert "And definitions are unsupported"
|
||||
of DefinitionKind.Pattern:
|
||||
nimTypeOf(scm, known, def.pattern, name)
|
||||
|
||||
proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: Definition) =
|
||||
discard
|
||||
|
||||
proc literalToPreserveCall(scm: Schema; pr: Preserve): PNode =
|
||||
var prConstr = nn(nkObjConstr, preserveIdent(scm))
|
||||
proc constr(kind, field: string; lit: PNode) =
|
||||
prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind))
|
||||
prConstr.add nn(nkExprColonExpr, ident(field), lit)
|
||||
case pr.orKind
|
||||
of pkBoolean:
|
||||
constr($pr.orKind, "bool", if pr.bool: ident"true" else: ident"false")
|
||||
of pkFloat:
|
||||
constr($pr.orKind, "float", newFloatNode(nkFloat32Lit, pr.float))
|
||||
of pkDouble:
|
||||
constr($pr.orKind, "double", newFloatNode(nkFloat64Lit, pr.double))
|
||||
of pkSignedInteger:
|
||||
constr($pr.orKind, "int", newIntNode(nkInt64Lit, pr.int))
|
||||
of pkString:
|
||||
constr($pr.orKind, "string", newStrNode(nkTripleStrLit, pr.string))
|
||||
of pkByteString:
|
||||
return nn(nkCall, ident"parsePreserves", newStrNode(nkTripleStrLit, $pr))
|
||||
of pkSymbol:
|
||||
constr($pr.orKind, "symbol", newStrNode(nkStrLit, pr.symbol))
|
||||
else:
|
||||
raise newException(ValueError, "refusing to convert to a literal: " & $pr)
|
||||
prConstr
|
||||
|
||||
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; pat: Pattern) =
|
||||
discard
|
||||
|
||||
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; def: Definition) =
|
||||
discard
|
||||
|
||||
proc collectRefImports(imports: PNode; pat: Pattern)
|
||||
|
||||
proc collectRefImports(imports: PNode; sp: SimplePattern) =
|
||||
case sp.orKind
|
||||
of SimplePatternKind.dictof:
|
||||
imports.add ident"std/tables"
|
||||
of SimplePatternKind.Ref:
|
||||
if sp.`ref`.module != @[]:
|
||||
imports.add ident(sp.ref.module[0])
|
||||
else: discard
|
||||
|
||||
proc collectRefImports(imports: PNode; cp: CompoundPattern) =
|
||||
case cp.orKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
collectRefImports(imports, cp.rec.label.pattern)
|
||||
collectRefImports(imports, cp.rec.fields.pattern)
|
||||
of CompoundPatternKind.`tuple`:
|
||||
for p in cp.tuple.patterns: collectRefImports(imports, p.pattern)
|
||||
of CompoundPatternKind.`tupleprefix`:
|
||||
for np in cp.tupleprefix.fixed: collectRefImports(imports, np.pattern)
|
||||
collectRefImports(imports, cp.tupleprefix.variable.pattern)
|
||||
of CompoundPatternKind.`dict`:
|
||||
for nsp in cp.dict.entries.values:
|
||||
collectRefImports(imports, nsp.pattern)
|
||||
|
||||
proc collectRefImports(imports: PNode; pat: Pattern) =
|
||||
case pat.orKind
|
||||
of PatternKind.SimplePattern:
|
||||
collectRefImports(imports, pat.simplePattern)
|
||||
of PatternKind.CompoundPattern:
|
||||
collectRefImports(imports, pat.compoundPattern)
|
||||
|
||||
proc collectRefImports(imports: PNode; def: Definition) =
|
||||
case def.orKind
|
||||
of DefinitionKind.`or`:
|
||||
collectRefImports(imports, def.or.data.pattern0.pattern)
|
||||
collectRefImports(imports, def.or.data.pattern1.pattern)
|
||||
for na in def.or.data.patternN:
|
||||
collectRefImports(imports, na.pattern)
|
||||
of DefinitionKind.`and`:
|
||||
collectRefImports(imports, def.and.data.pattern0.pattern)
|
||||
collectRefImports(imports, def.and.data.pattern1.pattern)
|
||||
for np in def.and.data.patternN:
|
||||
collectRefImports(imports, np.pattern)
|
||||
of DefinitionKind.Pattern:
|
||||
collectRefImports(imports, def.pattern)
|
||||
|
||||
proc collectRefImports(imports: PNode; scm: Schema) =
|
||||
for _, def in scm.data.definitions:
|
||||
collectRefImports(imports, def)
|
||||
|
||||
proc renderNimModule*(scm: Schema): string =
|
||||
## Construct and render a Nim module from a `Schema`.
|
||||
var
|
||||
typeDefs: TypeTable
|
||||
typeSection = newNode nkTypeSection
|
||||
procs: seq[PNode]
|
||||
megaType: PNode
|
||||
for name, def in scm.data.definitions.pairs:
|
||||
if isLiteral(scm, def):
|
||||
generateConstProcs(procs, scm, name, def)
|
||||
else:
|
||||
var name = name
|
||||
name[0] = name[0].toUpperAscii
|
||||
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
|
||||
if not isSymbolEnum(scm, def):
|
||||
if megaType.isNil:
|
||||
megaType = defIdent
|
||||
else:
|
||||
megaType = nn(nkInfix,
|
||||
ident"|", megaType, defIdent)
|
||||
let typeSpec = nimTypeOf(scm, typeDefs, def, name)
|
||||
typeDefs[name] = typeDef(scm, name, def, typeSpec.node)
|
||||
generateProcs(procs, scm, name, def)
|
||||
for td in typeDefs.values:
|
||||
typeSection.add td
|
||||
var imports = nkImportStmt.newNode.add(
|
||||
ident"std/typetraits",
|
||||
ident"preserves")
|
||||
collectRefImports(imports, scm)
|
||||
let dollarGenericParams =
|
||||
if isEmbeddable(scm):
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty()))
|
||||
else: newEmpty()
|
||||
procs.add nn(nkProcDef,
|
||||
"$".toFieldIdent,
|
||||
newEmpty(),
|
||||
dollarGenericParams,
|
||||
nn(nkFormalParams,
|
||||
ident"string",
|
||||
nn(nkIdentDefs,
|
||||
ident"x",
|
||||
megaType,
|
||||
newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList,
|
||||
nn(nkCall, ident"$",
|
||||
nn(nkCall, ident"toPreserve", ident"x"))))
|
||||
procs.add nn(nkProcDef,
|
||||
"encode".ident.toExport,
|
||||
newEmpty(),
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
|
||||
nn(nkFormalParams,
|
||||
nn(nkBracketExpr, ident"seq", ident"byte"),
|
||||
nn(nkIdentDefs,
|
||||
ident"x",
|
||||
megaType,
|
||||
newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList,
|
||||
nn(nkCall, ident"encode", nn(nkCall,
|
||||
ident"toPreserve", ident"x", ident"E"))))
|
||||
var module = newNode(nkStmtList).add(
|
||||
imports,
|
||||
typeSection
|
||||
).add(procs)
|
||||
renderTree(module, {renderNone, renderIr})
|
||||
|
||||
when isMainModule:
|
||||
proc writeModule(scm: Schema; path: string) =
|
||||
let txt = renderNimModule(scm)
|
||||
writeFile(path, txt)
|
||||
stdout.writeLine(path)
|
||||
|
||||
import std/[options, os, parseopt]
|
||||
var inputs: seq[string]
|
||||
for kind, key, val in getopt():
|
||||
case kind
|
||||
of cmdLongOption:
|
||||
case key
|
||||
else: quit("unhandled option " & key)
|
||||
of cmdShortOption:
|
||||
case key
|
||||
else: quit("unhandled option " & key)
|
||||
of cmdArgument:
|
||||
inputs.add absolutePath(key)
|
||||
of cmdEnd: discard
|
||||
for filepath in inputs:
|
||||
var useful: bool
|
||||
let pr = decodePreserves(readFile filepath)
|
||||
preserveTo(pr, Schema).map do (scm: Schema):
|
||||
useful = true
|
||||
let
|
||||
(_, name, _) = splitFile(filepath)
|
||||
outputPath = name & ".nim"
|
||||
writeModule(scm, outputPath)
|
||||
preserveTo(pr, Bundle).map do (bundle: Bundle):
|
||||
useful = true
|
||||
for modPath, scm in bundle.modules:
|
||||
let path = joinPath(modPath) & ".nim"
|
||||
writeModule(scm, path)
|
||||
if not useful:
|
||||
quit "Failed to recognized " & filepath
|
|
@ -0,0 +1,7 @@
|
|||
import std/macros
|
||||
|
||||
|
||||
macro dot*(obj: object, fld: string): untyped =
|
||||
## Turn ``obj.dot("fld")`` into ``obj.fld``.
|
||||
|
||||
newDotExpr(obj, newIdentNode(fld.strVal))
|
|
@ -1,730 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[os, strutils, sets, tables]
|
||||
|
||||
import compiler/[ast, idents, renderer, lineinfos]
|
||||
|
||||
import ../../preserves, ../schemas
|
||||
|
||||
type
|
||||
Value = Preserve[void]
|
||||
TypeSpec = tuple[node: PNode, embeddable: bool]
|
||||
TypeTable = OrderedTable[string, TypeSpec]
|
||||
|
||||
proc add(parent, child: PNode): PNode {.discardable.} =
|
||||
parent.sons.add child
|
||||
parent
|
||||
|
||||
proc add(parent: PNode; children: varargs[PNode]): PNode {.discardable.} =
|
||||
parent.sons.add children
|
||||
parent
|
||||
|
||||
proc child(sn: SchemaNode): SchemaNode =
|
||||
assert(sn.nodes.len == 1)
|
||||
sn.nodes[0]
|
||||
|
||||
proc nn(kind: TNodeKind; children: varargs[PNode]): PNode =
|
||||
result = newNode(kind)
|
||||
result.sons.add(children)
|
||||
|
||||
proc nn(kind: TNodeKind; child: PNode): PNode =
|
||||
result = newNode(kind)
|
||||
result.sons.add(child)
|
||||
|
||||
proc ident(s: string): PNode =
|
||||
newIdentNode(PIdent(s: s), TLineInfo())
|
||||
|
||||
proc accQuote(n: PNode): Pnode =
|
||||
nkAccQuoted.newNode.add(n)
|
||||
|
||||
proc ident(sn: SchemaNode): PNode =
|
||||
var s: string
|
||||
case sn.kind
|
||||
of snkAlt:
|
||||
s = sn.altLabel.nimIdentNormalize
|
||||
s[0] = s[0].toLowerAscii
|
||||
of snkLiteral: s = $sn.value
|
||||
of snkRecord:
|
||||
s = sn.nodes[0].value.symbol
|
||||
of snkNamed:
|
||||
s = sn.name
|
||||
of snkDictionary, snkVariableTuple:
|
||||
s = "data"
|
||||
else:
|
||||
raiseAssert("no ident for " & $sn.kind & " " & $sn)
|
||||
s.ident.accQuote
|
||||
|
||||
proc parameterize(node: PNode; embeddable: bool): PNode =
|
||||
if embeddable: nn(nkBracketExpr, node, ident"E")
|
||||
else: node
|
||||
|
||||
proc parameterize(spec: TypeSpec): PNode =
|
||||
parameterize(spec.node, spec.embeddable)
|
||||
|
||||
proc isPreserve(n: PNode): bool =
|
||||
n.kind == nkBracketExpr and
|
||||
n.renderTree == "Preserve[E]"
|
||||
|
||||
proc preserveIdent(): Pnode =
|
||||
nn(nkBracketExpr, ident"Preserve", ident"E")
|
||||
|
||||
proc orEmbed(x: var TypeSpec; y: TypeSpec) =
|
||||
x.embeddable = x.embeddable or y.embeddable
|
||||
|
||||
proc isEmbeddable(scm: Schema; sn: SchemaNode; seen: var HashSet[string]): bool =
|
||||
case sn.kind
|
||||
of snkAtom, snkLiteral: discard
|
||||
of snkAlt:
|
||||
result = isEmbeddable(scm, sn.altBranch, seen)
|
||||
of snkAny: result = true
|
||||
of snkRef:
|
||||
if sn.refPath.len == 1:
|
||||
let name = sn.refPath[0]
|
||||
if name notin seen: # loop detection
|
||||
seen.incl name
|
||||
result = isEmbeddable(scm, scm.definitions[name], seen)
|
||||
else:
|
||||
result = false
|
||||
# TODO: cross-module types
|
||||
of snkEmbedded:
|
||||
result = isEmbeddable(scm, sn.embed, seen)
|
||||
of snkNamed:
|
||||
result = isEmbeddable(scm, sn.pattern, seen)
|
||||
else:
|
||||
for bn in sn.nodes:
|
||||
result = isEmbeddable(scm, bn, seen)
|
||||
if result: break
|
||||
|
||||
proc isEmbeddable(scm: Schema; sn: SchemaNode): bool =
|
||||
var seen: HashSet[string]
|
||||
isEmbeddable(scm, sn, seen)
|
||||
|
||||
proc isConst(scm: Schema; sn: SchemaNode): bool =
|
||||
case sn.kind
|
||||
of snkLiteral: result = true
|
||||
of snkRef:
|
||||
if sn.refPath.len == 1:
|
||||
result = isConst(scm, scm.definitions[sn.refPath[0]])
|
||||
else: discard
|
||||
|
||||
proc isSymbolEnum(scm: Schema; sn: SchemaNode): bool =
|
||||
case sn.kind
|
||||
of snkRef:
|
||||
if sn.refPath.len == 1:
|
||||
result = isSymbolEnum(scm, scm.definitions[sn.refPath[0]])
|
||||
of snkOr:
|
||||
for bn in sn.nodes:
|
||||
if bn.altBranch.kind != snkLiteral or
|
||||
bn.altBranch.value.kind != pkSymbol:
|
||||
return false
|
||||
result = true
|
||||
else: discard
|
||||
|
||||
proc typeIdent(scm: Schema; sn: SchemaNode): TypeSpec =
|
||||
case sn.kind
|
||||
of snkAtom:
|
||||
case sn.atom
|
||||
of akBool: (ident"bool", false)
|
||||
of akFloat: (ident"float32", false)
|
||||
of akDouble: (ident"float64", false)
|
||||
of akInt: (ident"BiggestInt", false)
|
||||
of akString: (ident"string", false)
|
||||
of akBytes: (nn(nkBracketExpr, ident"seq", ident"byte"), false)
|
||||
of akSymbol: (ident"string", false) # TODO: distinct string type for symbols?
|
||||
of snkNamed:
|
||||
typeIdent(scm, sn.pattern)
|
||||
of snkRef:
|
||||
var id = ident sn.refPath[sn.refPath.high]
|
||||
for i in countDown(sn.refPath.high.pred, 0):
|
||||
id = nn(nkDotExpr, ident(sn.refPath[i]), id)
|
||||
(id, isEmbeddable(scm, sn))
|
||||
else:
|
||||
(preserveIdent(), true)
|
||||
|
||||
proc toExport(n: sink PNode): PNode =
|
||||
nkPostFix.newNode.add(ident"*", n)
|
||||
|
||||
proc newEmpty(): PNode = newNode(nkEmpty)
|
||||
|
||||
proc literal(scm: Schema; sn: SchemaNode): Value =
|
||||
case sn.kind
|
||||
of snkLiteral: result = sn.value
|
||||
of snkRef:
|
||||
if sn.refPath.len == 1:
|
||||
result = literal(scm, scm.definitions[sn.refPath[0]])
|
||||
else:
|
||||
raiseAssert("not convertable to a literal: " & $sn)
|
||||
else:
|
||||
raiseAssert("not convertable to a literal: " & $sn)
|
||||
|
||||
proc toEnumTy(sn: SchemaNode): PNode =
|
||||
result = nkEnumTy.newNode.add newEmpty()
|
||||
for bn in sn.nodes:
|
||||
result.add bn.altLabel.nimIdentNormalize.ident.accQuote
|
||||
|
||||
proc toEnumDef(name: string; sn: SchemaNode): PNode =
|
||||
nkTypeDef.newNode.add(
|
||||
nkPragmaExpr.newNode.add(
|
||||
name.ident.toExport,
|
||||
nkPragma.newNode.add(ident"pure")),
|
||||
newEmpty(),
|
||||
sn.toEnumTy)
|
||||
|
||||
proc embeddingParams(embeddable: bool): PNode =
|
||||
if embeddable:
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void"))
|
||||
else:
|
||||
newEmpty()
|
||||
|
||||
proc identDef(sn: SchemaNode; a, b: PNode; embeddable: bool): PNode =
|
||||
if embeddable and b.kind != nkBracketExpr:
|
||||
nn(nkIdentDefs, a, nn(nkBracketExpr, b, ident"E"), newEmpty())
|
||||
else:
|
||||
nn(nkIdentDefs, a, b, newEmpty())
|
||||
|
||||
proc identDef(sn: SchemaNode; l: PNode; ts: TypeSpec): PNode =
|
||||
identDef(sn, l, ts.node, ts.embeddable)
|
||||
|
||||
proc typeDef(sn: SchemaNode; name: string; ty: PNode; embeddable: bool): PNode =
|
||||
case sn.kind
|
||||
of snkRecord:
|
||||
nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
name.ident.toExport,
|
||||
nn(nkPragma,
|
||||
nn(nkExprColonExpr,
|
||||
ident"record",
|
||||
PNode(kind: nkStrLit, strVal: sn.nodes[0].value.symbol)))),
|
||||
embeddingParams(embeddable),
|
||||
ty)
|
||||
else:
|
||||
nn(nkTypeDef, name.ident.toExport, embeddingParams(embeddable), ty)
|
||||
|
||||
proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""):
|
||||
TypeSpec =
|
||||
if name in known: return known[name]
|
||||
case sn.kind
|
||||
of snkOr:
|
||||
if isSymbolEnum(scm, sn):
|
||||
result.node = sn.toEnumTy
|
||||
else:
|
||||
let
|
||||
enumName = name.nimIdentNormalize & "Kind"
|
||||
enumIdent = ident(enumName)
|
||||
if enumName notin known:
|
||||
known[enumName] = (toEnumDef(enumName, sn), false)
|
||||
let recCase = nkRecCase.newNode.add(
|
||||
nkIdentDefs.newNode.add(
|
||||
"kind".ident.toExport,
|
||||
enumName.ident,
|
||||
newEmpty()))
|
||||
for bn in sn.nodes:
|
||||
assert(bn.kind == snkAlt, $bn.kind)
|
||||
doAssert(name != "", " no name for " & $sn)
|
||||
var memberType: TypeSpec
|
||||
if bn.altbranch.kind == snkRef:
|
||||
memberType = typeIdent(scm, bn.altBranch)
|
||||
else:
|
||||
let memberTypeName = name & bn.altLabel.nimIdentNormalize
|
||||
memberType.node = ident memberTypeName
|
||||
if memberTypeName notin known:
|
||||
let ty = nimTypeOf(scm, known, bn.altBranch, memberTypeName)
|
||||
orEmbed memberType, ty
|
||||
orEmbed result, memberType
|
||||
known[memberTypeName] = (
|
||||
typeDef(bn.altBranch, memberTypeName, ty.node, ty.embeddable),
|
||||
ty.embeddable)
|
||||
var recList = nkRecList.newNode
|
||||
case bn.altBranch.kind
|
||||
of snkRecord:
|
||||
case bn.altBranch.nodes.len
|
||||
of 0, 1: discard
|
||||
of 2:
|
||||
if not isConst(scm, bn.altBranch.nodes[1]):
|
||||
let label = bn.ident
|
||||
let branch = typeIdent(scm, bn.altBranch.nodes[1])
|
||||
orEmbed result, branch
|
||||
recList.add identDef(bn, label.toExport, branch)
|
||||
else:
|
||||
recList.add identDef(bn, bn.ident.toExport, memberType)
|
||||
else:
|
||||
if isConst(scm, bn.altBranch):
|
||||
recList.add nkDiscardStmt.newNode.add(newEmpty())
|
||||
else:
|
||||
let label = bn.ident
|
||||
let branch = typeIdent(scm, bn.altBranch)
|
||||
orEmbed result, branch
|
||||
recList.add identDef(bn, label.toExport, branch)
|
||||
let disc = nkDotExpr.newNode.add(
|
||||
enumIdent, bn.altLabel.nimIdentNormalize.ident.accQuote)
|
||||
if recList.len == 0:
|
||||
recList.add identDef(bn, bn.ident.toExport, memberType)
|
||||
recCase.add nkOfBranch.newNode.add(disc, recList)
|
||||
result.node = nn(nkRefTy, nn(nkObjectTy,
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkRecList, recCase)))
|
||||
of snkAny:
|
||||
result = (ident"Preserve", true)
|
||||
of snkAtom:
|
||||
result = typeIdent(scm, sn)
|
||||
of snkEmbedded:
|
||||
result = nimTypeOf(scm, known, sn.embed)
|
||||
of snkLiteral:
|
||||
result.node = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim
|
||||
of pkBoolean: ident"bool"
|
||||
of pkFloat: ident"float32"
|
||||
of pkDouble: ident"float64"
|
||||
of pkSignedInteger: ident"BiggestInt"
|
||||
of pkBigInteger: ident"BigInt"
|
||||
of pkString: ident"string"
|
||||
of pkByteString: nn(
|
||||
nkBracketExpr, ident"seq", ident"byte")
|
||||
of pkSymbol: ident"string"
|
||||
of pkRecord: preserveIdent()
|
||||
of pkSequence: nn(
|
||||
nkBracketExpr, ident"seq", preserveIdent())
|
||||
of pkSet: nn(
|
||||
nkBracketExpr, ident"HashSet", preserveIdent())
|
||||
of pkDictionary: nn(
|
||||
nkBracketExpr, ident"TableRef", preserveIdent(), preserveIdent())
|
||||
of pkEmbedded:
|
||||
raiseAssert "this should never happen"
|
||||
of snkSequenceOf:
|
||||
result = nimTypeOf(scm, known, sn.child)
|
||||
result.node = nkBracketExpr.newNode.add(
|
||||
ident"seq",
|
||||
parameterize(result))
|
||||
of snkSetOf:
|
||||
result = nimTypeOf(scm, known, sn.child)
|
||||
result.node = nkBracketExpr.newNode.add(
|
||||
ident"HashedSet",
|
||||
parameterize(result))
|
||||
of snkDictOf:
|
||||
let
|
||||
key = nimTypeOf(scm, known, sn.nodes[0])
|
||||
val = nimTypeOf(scm, known, sn.nodes[1])
|
||||
orEmbed result, key
|
||||
orEmbed result, val
|
||||
result.node = nkBracketExpr.newNode.add(
|
||||
ident"TableRef", parameterize(key), parameterize(val))
|
||||
of snkRecord:
|
||||
case sn.nodes.len
|
||||
of 0, 1:
|
||||
result.node = nn(nkObjectTy,
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkRecList, nn(nkDiscardStmt, newEmpty())))
|
||||
else:
|
||||
let recList = nkRecList.newNode()
|
||||
for i, field in sn.nodes:
|
||||
if i > 0:
|
||||
let
|
||||
id = field.ident
|
||||
fieldType = nimTypeOf(scm, known, field, $id)
|
||||
orEmbed result, fieldType
|
||||
recList.add identDef(sn, id.toExport, fieldType)
|
||||
result.node = nn(nkRefTy, nn(nkObjectTy,
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
recList))
|
||||
of snkTuple:
|
||||
result.node = nkTupleTy.newNode
|
||||
for tn in sn.nodes:
|
||||
if not isConst(scm, sn):
|
||||
let fieldType = nimTypeOf(scm, known, tn)
|
||||
orEmbed result, fieldType
|
||||
result.node.add identDef(sn, tn.ident, fieldType)
|
||||
of snkVariableTuple:
|
||||
result.node = nkTupleTy.newNode
|
||||
for i, tn in sn.nodes:
|
||||
if not isConst(scm, sn):
|
||||
let fieldType = nimTypeOf(scm, known, tn)
|
||||
orEmbed result, fieldType
|
||||
if i == sn.nodes.high:
|
||||
result.node.add identDef(
|
||||
tn,
|
||||
tn.ident,
|
||||
nn(nkBracketExpr, ident"seq", fieldType.node),
|
||||
fieldType.embeddable)
|
||||
else:
|
||||
result.node.add identDef(tn, tn.ident, fieldType)
|
||||
of snkDictionary:
|
||||
result.node = nkTupleTy.newNode
|
||||
for i in countup(0, sn.nodes.high, 2):
|
||||
let id = ident(sn.nodes[i+0])
|
||||
let fieldType = nimTypeOf(scm, known, sn.nodes[i+1], $id)
|
||||
orEmbed result, fieldType
|
||||
result.node.add identDef(sn.nodes[i+1], id, fieldType)
|
||||
of snkNamed:
|
||||
result = nimTypeOf(scm, known, sn.pattern, name)
|
||||
of snkRef:
|
||||
result = typeIdent(scm, sn)
|
||||
else:
|
||||
result.node = nkCommentStmt.newNode
|
||||
result.node.comment = result.node.comment &
|
||||
"Missing type generator for " & $sn.kind & " " & $sn
|
||||
|
||||
proc exportIdent(id: string): PNode = nn(nkPostFix, ident"*", ident(id))
|
||||
|
||||
proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: SchemaNode) =
|
||||
case def.kind
|
||||
of snkLiteral:
|
||||
var stmts = nn(nkStmtList)
|
||||
case def.value.kind
|
||||
of pkSignedInteger:
|
||||
discard stmts.add newIntNode(nkIntLit, def.value.int)
|
||||
of pkSymbol:
|
||||
discard stmts.add nn(nkCall,
|
||||
nn(nkBracketExpr, ident"symbol", ident"E"),
|
||||
PNode(kind: nkStrLit, strVal: def.value.symbol))
|
||||
else:
|
||||
raiseAssert("conversion of " & $def & " to a Nim literal is not implemented")
|
||||
var procId = name
|
||||
procId[0] = procId[0].toLowerAscii
|
||||
let constProc= nn(nkProcDef,
|
||||
exportIdent(procId),
|
||||
newEmpty(),
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
|
||||
nn(nkFormalParams, preserveIdent()),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
stmts)
|
||||
constProc.comment = "``" & $def & "``"
|
||||
result.add constProc
|
||||
else: discard
|
||||
|
||||
proc nimLit(scm: Schema; sn: SchemaNode): PNode =
|
||||
assert(sn.kind == snkLiteral, $sn)
|
||||
case sn.value.kind
|
||||
of pkSymbol:
|
||||
nn(nkCall,
|
||||
nn(nkBracketExpr, ident"symbol", ident"E"),
|
||||
PNode(kind: nkStrLit, strVal: sn.value.symbol))
|
||||
else:
|
||||
raiseAssert("no Nim literal for " & $sn)
|
||||
|
||||
proc literalToPreserveCall(pr: Preserve): PNode =
|
||||
var prConstr = nn(nkObjConstr, preserveIdent())
|
||||
proc constr(kind, field: string; lit: PNode) =
|
||||
prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind))
|
||||
prConstr.add nn(nkExprColonExpr, ident(field), lit)
|
||||
case pr.kind
|
||||
of pkBoolean:
|
||||
constr($pr.kind, "bool", if pr.bool: ident"true" else: ident"false")
|
||||
of pkFloat:
|
||||
constr($pr.kind, "float", newFloatNode(nkFloat32Lit, pr.float))
|
||||
of pkDouble:
|
||||
constr($pr.kind, "double", newFloatNode(nkFloat64Lit, pr.double))
|
||||
of pkSignedInteger:
|
||||
constr($pr.kind, "int", newIntNode(nkInt64Lit, pr.int))
|
||||
of pkString:
|
||||
constr($pr.kind, "string", newStrNode(nkTripleStrLit, pr.string))
|
||||
of pkByteString:
|
||||
return nn(nkCall, ident"parsePreserves", newStrNode(nkTripleStrLit, $pr))
|
||||
of pkSymbol:
|
||||
constr($pr.kind, "symbol", newStrNode(nkStrLit, pr.symbol))
|
||||
else:
|
||||
raise newException(ValueError, "refusing to convert to a literal: " & $pr)
|
||||
prConstr
|
||||
|
||||
proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
|
||||
let seqBracket = nn(nkBracket)
|
||||
for i, field in sn.nodes:
|
||||
if isConst(scm, field):
|
||||
seqBracket.add literalToPreserveCall(literal(scm, field))
|
||||
elif sn.kind == snkTuple or i < sn.nodes.high:
|
||||
seqBracket.add nn(nkCall,
|
||||
ident"toPreserve", nn(nkDotExpr, ident, field.ident), ident"E")
|
||||
let seqConstr = nn(nkPrefix, ident"@", seqBracket)
|
||||
let colonExpr = nn(nkExprColonExpr, ident"sequence")
|
||||
if sn.kind == snkTuple:
|
||||
colonExpr.add seqConstr
|
||||
else:
|
||||
colonExpr.add nn(nkInfix,
|
||||
ident"&",
|
||||
seqConstr,
|
||||
nn(nkDotExpr,
|
||||
nn(nkCall, ident"toPreserve",
|
||||
nn(nkDotExpr,
|
||||
ident, sn.nodes[sn.nodes.high].ident),
|
||||
ident"E"),
|
||||
ident"sequence"))
|
||||
nn(nkObjConstr,
|
||||
preserveIdent(),
|
||||
nn(nkExprColonExpr, ident"kind", ident"pkSequence"),
|
||||
colonExpr)
|
||||
|
||||
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: SchemaNode) =
|
||||
case sn.kind
|
||||
of snkOr:
|
||||
var
|
||||
enumId = name.ident
|
||||
paramId = ident"v"
|
||||
orStmts = nn(nkStmtList)
|
||||
if isSymbolEnum(scm, sn):
|
||||
let caseStmt = nn(nkCaseStmt, paramId)
|
||||
for bn in sn.nodes:
|
||||
caseStmt.add nn(nkOfBranch,
|
||||
nn(nkDotExpr,
|
||||
enumId,
|
||||
bn.altLabel.nimIdentNormalize.ident.accQuote),
|
||||
nn(nkCall,
|
||||
nn(nkBracketExpr, ident"symbol", ident"E"),
|
||||
PNode(kind: nkStrLit, strVal: $bn.altLabel)))
|
||||
orStmts.add caseStmt
|
||||
else:
|
||||
let caseStmt = nn(nkCaseStmt, nn(nkDotExpr, paramId, ident"kind"))
|
||||
proc genStmts(stmts: PNode; fieldId: PNode; sn: SchemaNode) =
|
||||
case sn.kind
|
||||
of snkLiteral:
|
||||
stmts.add literalToPreserveCall(literal(scm, sn))
|
||||
of snkOr, snkRecord, snkRef:
|
||||
if sn.kind == snkRef and sn.refPath.len == 1:
|
||||
let refDef = scm.definitions[sn.refPath[0]]
|
||||
genStmts(stmts, fieldId, refDef)
|
||||
else:
|
||||
stmts.add nn(nkCall,
|
||||
ident"toPreserve",
|
||||
nn(nkDotExpr, paramId, fieldId), ident"E")
|
||||
of snkTuple, snkVariableTuple:
|
||||
stmts.add tupleConstructor(scm, sn, nn(nkDotExpr, paramId, fieldId))
|
||||
of snkAtom, snkSequenceOf:
|
||||
stmts.add nn(nkCall,
|
||||
ident"toPreserve",
|
||||
nn(nkDotExpr, paramId, fieldId), ident"E")
|
||||
else:
|
||||
raiseAssert("no case statement for " & $sn.kind & " " & $sn)
|
||||
for bn in sn.nodes:
|
||||
let stmts = nn(nkStmtList)
|
||||
genStmts(stmts, bn.ident, bn.altBranch)
|
||||
caseStmt.add nn(nkOfBranch,
|
||||
nn(nkDotExpr,
|
||||
ident(name & "Kind"),
|
||||
bn.altLabel.nimIdentNormalize.ident.accQuote),
|
||||
stmts)
|
||||
orStmts.add caseStmt
|
||||
result.add nn(nkProcDef,
|
||||
exportIdent("toPreserveHook"),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkFormalParams,
|
||||
preserveIdent(),
|
||||
nn(nkIdentDefs,
|
||||
paramId, ident(name), newEmpty()),
|
||||
nn(nkIdentDefs,
|
||||
ident"E", ident"typedesc", newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
orStmts)
|
||||
of snkRecord:
|
||||
var
|
||||
params = nn(nkFormalParams, preserveIdent())
|
||||
initRecordCall = nn(nkCall,
|
||||
nn(nkBracketExpr, ident"initRecord", ident"E"),
|
||||
nimLit(scm, sn.nodes[0]))
|
||||
for i, field in sn.nodes:
|
||||
if i > 0:
|
||||
let
|
||||
id = field.ident
|
||||
var (fieldType, embeddable) = typeIdent(scm, field)
|
||||
if not fieldType.isPreserve:
|
||||
fieldType =
|
||||
nn(nkInfix,
|
||||
ident"|",
|
||||
fieldType,
|
||||
preserveIdent())
|
||||
params.add nn(nkIdentDefs,
|
||||
id, fieldType, newEmpty())
|
||||
initRecordCall.add(
|
||||
nn(nkCall, ident"toPreserve", id, ident"E"))
|
||||
var procId = name
|
||||
procId[0] = procId[0].toLowerAscii
|
||||
let cmt = nkCommentStmt.newNode
|
||||
cmt.comment = "Preserves constructor for ``" & name & "``."
|
||||
result.add nn(nkProcDef,
|
||||
procId.ident.accQuote.toExport,
|
||||
newEmpty(),
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
|
||||
params,
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList,
|
||||
cmt, initRecordCall))
|
||||
block:
|
||||
let paramId = name.toLowerAscii.ident.accQuote
|
||||
initRecordCall = nn(nkCall,
|
||||
nn(nkBracketExpr, ident"initRecord", ident"E"),
|
||||
nimLit(scm, sn.nodes[0]))
|
||||
for i, field in sn.nodes:
|
||||
if i > 0:
|
||||
initRecordCall.add nn(nkCall,
|
||||
ident"toPreserve",
|
||||
nn(nkDotExpr, paramId, field.ident),
|
||||
ident"E")
|
||||
result.add nn(nkProcDef,
|
||||
exportIdent("toPreserveHook"),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkFormalParams,
|
||||
preserveIdent(),
|
||||
nn(nkIdentDefs,
|
||||
paramId, ident(name), newEmpty()),
|
||||
nn(nkIdentDefs,
|
||||
ident"E", ident"typedesc", newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList, initRecordCall))
|
||||
of snkTuple, snkVariableTuple:
|
||||
let paramId = name.toLowerAscii.ident.accQuote
|
||||
result.add nn(nkProcDef,
|
||||
exportIdent("toPreserveHook"),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkFormalParams,
|
||||
preserveIdent(),
|
||||
nn(nkIdentDefs,
|
||||
paramId, ident(name), newEmpty()),
|
||||
nn(nkIdentDefs,
|
||||
ident"E", ident"typedesc", newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList, tupleConstructor(scm, sn, paramId)))
|
||||
else: discard
|
||||
|
||||
proc collectRefImports(imports: PNode; sn: SchemaNode) =
|
||||
case sn.kind
|
||||
of snkLiteral:
|
||||
if sn.value.isDictionary:
|
||||
imports.add ident"std/tables"
|
||||
of snkDictOf:
|
||||
imports.add ident"std/tables"
|
||||
of snkRef:
|
||||
if sn.refPath.len > 1:
|
||||
imports.add ident(sn.refPath[0])
|
||||
else:
|
||||
for child in sn.items:
|
||||
collectRefImports(imports, child)
|
||||
|
||||
proc collectRefImports(imports: PNode; scm: Schema) =
|
||||
for _, def in scm.definitions:
|
||||
collectRefImports(imports, def)
|
||||
|
||||
proc generateNimFile*(scm: Schema; path: string) =
|
||||
var
|
||||
knownTypes: TypeTable
|
||||
typeSection = newNode nkTypeSection
|
||||
procs: seq[PNode]
|
||||
megaType: PNode
|
||||
for name, def in scm.definitions.pairs:
|
||||
if isConst(scm, def):
|
||||
generateConstProcs(procs, scm, name, def)
|
||||
else:
|
||||
var name = name
|
||||
name[0] = name[0].toUpperAscii
|
||||
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
|
||||
if megaType.isNil:
|
||||
megaType = defIdent
|
||||
else:
|
||||
megaType = nn(nkInfix,
|
||||
ident"|", megaType, defIdent)
|
||||
let (t, embeddable) =
|
||||
if def.kind == snkAny: (preserveIdent(), true)
|
||||
else: nimTypeOf(scm, knownTypes, def, name)
|
||||
t.comment = "``" & $def & "``"
|
||||
case def.kind
|
||||
of snkAtom:
|
||||
knownTypes[name] = (nkTypeDef.newNode.add(
|
||||
name.ident.toExport, embeddingParams(false), t),
|
||||
embeddable)
|
||||
else:
|
||||
if def.kind == snkRecord:
|
||||
knownTypes[name] = (typeDef(def, name, t, embeddable), embeddable)
|
||||
else:
|
||||
case t.kind
|
||||
of nkEnumTy:
|
||||
knownTypes[name] = (nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
name.ident.toExport,
|
||||
nn(nkPragma, ident"pure")),
|
||||
newEmpty(),
|
||||
t), false)
|
||||
of nkDistinctTy:
|
||||
knownTypes[name] = (nn(nkTypeDef,
|
||||
nn(nkPragmaExpr,
|
||||
name.ident.toExport,
|
||||
nn(nkPragma,
|
||||
nn(nkExprColonExpr,
|
||||
ident"borrow",
|
||||
accQuote(ident".")))),
|
||||
newEmpty(),
|
||||
t), embeddable)
|
||||
else:
|
||||
knownTypes[name] = (nn(nkTypeDef,
|
||||
name.ident.toExport, embeddingParams(embeddable), t),
|
||||
embeddable)
|
||||
generateProcs(procs, scm, name, def)
|
||||
for (typeDef, _) in knownTypes.values:
|
||||
typeSection.add typeDef
|
||||
var imports = nkImportStmt.newNode.add(
|
||||
ident"std/typetraits",
|
||||
ident"preserves")
|
||||
collectRefImports(imports, scm)
|
||||
procs.add nn(nkProcDef,
|
||||
"$".ident.accQuote.toExport,
|
||||
newEmpty(),
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
|
||||
nn(nkFormalParams,
|
||||
ident"string",
|
||||
nn(nkIdentDefs,
|
||||
ident"x",
|
||||
megaType,
|
||||
newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList,
|
||||
nn(nkCall, ident"$",
|
||||
nn(nkCall, ident"toPreserve", ident"x", ident"E"))))
|
||||
procs.add nn(nkProcDef,
|
||||
"encode".ident.accQuote.toExport,
|
||||
newEmpty(),
|
||||
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
|
||||
nn(nkFormalParams,
|
||||
nn(nkBracketExpr, ident"seq", ident"byte"),
|
||||
nn(nkIdentDefs,
|
||||
ident"x",
|
||||
megaType,
|
||||
newEmpty())),
|
||||
newEmpty(),
|
||||
newEmpty(),
|
||||
nn(nkStmtList,
|
||||
nn(nkCall, ident"encode", nn(nkCall,
|
||||
ident"toPreserve", ident"x", ident"E"))))
|
||||
var module = newNode(nkStmtList).add(
|
||||
imports,
|
||||
typeSection
|
||||
).add(procs)
|
||||
writeFile(path, renderTree(module, {renderNone, renderIr}))
|
||||
|
||||
when isMainModule:
|
||||
import std/parseopt
|
||||
var inputs: seq[string]
|
||||
for kind, key, val in getopt():
|
||||
case kind
|
||||
of cmdLongOption:
|
||||
case key
|
||||
else: quit("unhandled option " & key)
|
||||
of cmdShortOption:
|
||||
case key
|
||||
else: quit("unhandled option " & key)
|
||||
of cmdArgument:
|
||||
inputs.add absolutePath(key)
|
||||
of cmdEnd: discard
|
||||
for filepath in inputs:
|
||||
let
|
||||
scm = parsePreservesSchema(readFile filepath, filepath)
|
||||
(dir, name, _) = splitFile(filepath)
|
||||
outputPath = dir / name & ".nim"
|
||||
generateNimFile(scm, outputPath)
|
||||
stdout.writeLine(outputPath)
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
import
|
||||
std/typetraits, preserves, std/tables
|
||||
|
||||
type
|
||||
Ref* {.preservesRecord: "ref".} = object
|
||||
`module`*: ModulePath
|
||||
`name`* {.preservesSymbol.}: string
|
||||
|
||||
ModulePath* = seq[string]
|
||||
Bundle* {.preservesRecord: "bundle".} = object
|
||||
`modules`*: Modules
|
||||
|
||||
CompoundPatternKind* {.pure.} = enum
|
||||
`rec`, `tuple`, `tuplePrefix`, `dict`
|
||||
CompoundPatternRec* {.preservesRecord: "rec".} = object
|
||||
`label`*: NamedPattern
|
||||
`fields`*: NamedPattern
|
||||
|
||||
CompoundPatternTuple* {.preservesRecord: "tuple".} = object
|
||||
`patterns`*: seq[NamedPattern]
|
||||
|
||||
CompoundPatternTuplePrefix* {.preservesRecord: "tuplePrefix".} = object
|
||||
`fixed`*: seq[NamedPattern]
|
||||
`variable`*: NamedSimplePattern
|
||||
|
||||
CompoundPatternDict* {.preservesRecord: "dict".} = object
|
||||
`entries`*: DictionaryEntries
|
||||
|
||||
`CompoundPattern`* {.preservesOr.} = ref object
|
||||
case orKind*: CompoundPatternKind
|
||||
of CompoundPatternKind.`rec`:
|
||||
`rec`*: CompoundPatternRec
|
||||
|
||||
of CompoundPatternKind.`tuple`:
|
||||
`tuple`*: CompoundPatternTuple
|
||||
|
||||
of CompoundPatternKind.`tuplePrefix`:
|
||||
`tupleprefix`*: CompoundPatternTuplePrefix
|
||||
|
||||
of CompoundPatternKind.`dict`:
|
||||
`dict`*: CompoundPatternDict
|
||||
|
||||
|
||||
Modules* = Table[ModulePath, Schema]
|
||||
EmbeddedTypeNameKind* {.pure.} = enum
|
||||
`Ref`, `false`
|
||||
`EmbeddedTypeName`* {.preservesOr.} = ref object
|
||||
case orKind*: EmbeddedTypeNameKind
|
||||
of EmbeddedTypeNameKind.`Ref`:
|
||||
`ref`*: Ref
|
||||
|
||||
of EmbeddedTypeNameKind.`false`:
|
||||
`false`* {.preservesLiteral: "#f".}: bool
|
||||
|
||||
|
||||
`AtomKind`* {.preservesOr.} = enum
|
||||
`Boolean`, `Float`, `Double`, `SignedInteger`, `String`, `ByteString`,
|
||||
`Symbol`
|
||||
Definitions* = Table[string, Definition]
|
||||
DictionaryEntries* = Table[Preserve[void], NamedSimplePattern]
|
||||
NamedPatternKind* {.pure.} = enum
|
||||
`named`, `anonymous`
|
||||
`NamedPattern`* {.preservesOr.} = ref object
|
||||
case orKind*: NamedPatternKind
|
||||
of NamedPatternKind.`named`:
|
||||
`named`*: Binding
|
||||
|
||||
of NamedPatternKind.`anonymous`:
|
||||
`anonymous`*: Pattern
|
||||
|
||||
|
||||
SimplePatternKind* {.pure.} = enum
|
||||
`any`, `atom`, `embedded`, `lit`, `seqof`, `setof`, `dictof`, `Ref`
|
||||
SimplePatternAtom* {.preservesRecord: "atom".} = object
|
||||
`atomKind`*: AtomKind
|
||||
|
||||
SimplePatternEmbedded* {.preservesRecord: "embedded".} = object
|
||||
`interface`*: SimplePattern
|
||||
|
||||
SimplePatternLit* {.preservesRecord: "lit".} = object
|
||||
`value`*: Preserve[void]
|
||||
|
||||
SimplePatternSeqof* {.preservesRecord: "seqof".} = object
|
||||
`pattern`*: SimplePattern
|
||||
|
||||
SimplePatternSetof* {.preservesRecord: "setof".} = object
|
||||
`pattern`*: SimplePattern
|
||||
|
||||
SimplePatternDictof* {.preservesRecord: "dictof".} = object
|
||||
`key`*: SimplePattern
|
||||
`value`*: SimplePattern
|
||||
|
||||
`SimplePattern`* {.preservesOr.} = ref object
|
||||
case orKind*: SimplePatternKind
|
||||
of SimplePatternKind.`any`:
|
||||
`any`* {.preservesLiteral: "any".}: bool
|
||||
|
||||
of SimplePatternKind.`atom`:
|
||||
`atom`*: SimplePatternAtom
|
||||
|
||||
of SimplePatternKind.`embedded`:
|
||||
`embedded`*: SimplePatternEmbedded
|
||||
|
||||
of SimplePatternKind.`lit`:
|
||||
`lit`*: SimplePatternLit
|
||||
|
||||
of SimplePatternKind.`seqof`:
|
||||
`seqof`*: SimplePatternSeqof
|
||||
|
||||
of SimplePatternKind.`setof`:
|
||||
`setof`*: SimplePatternSetof
|
||||
|
||||
of SimplePatternKind.`dictof`:
|
||||
`dictof`*: SimplePatternDictof
|
||||
|
||||
of SimplePatternKind.`Ref`:
|
||||
`ref`*: Ref
|
||||
|
||||
|
||||
NamedSimplePatternKind* {.pure.} = enum
|
||||
`named`, `anonymous`
|
||||
`NamedSimplePattern`* {.preservesOr.} = ref object
|
||||
case orKind*: NamedSimplePatternKind
|
||||
of NamedSimplePatternKind.`named`:
|
||||
`named`*: Binding
|
||||
|
||||
of NamedSimplePatternKind.`anonymous`:
|
||||
`anonymous`*: SimplePattern
|
||||
|
||||
|
||||
DefinitionKind* {.pure.} = enum
|
||||
`or`, `and`, `Pattern`
|
||||
DefinitionOrData* {.preservesTuple.} = object
|
||||
`pattern0`*: NamedAlternative
|
||||
`pattern1`*: NamedAlternative
|
||||
`patternN`* {.preservesTupleTail.}: seq[NamedAlternative]
|
||||
|
||||
DefinitionOr* {.preservesRecord: "or".} = object
|
||||
`data`*: DefinitionOrData
|
||||
|
||||
DefinitionAndData* {.preservesTuple.} = object
|
||||
`pattern0`*: NamedPattern
|
||||
`pattern1`*: NamedPattern
|
||||
`patternN`* {.preservesTupleTail.}: seq[NamedPattern]
|
||||
|
||||
DefinitionAnd* {.preservesRecord: "and".} = object
|
||||
`data`*: DefinitionAndData
|
||||
|
||||
`Definition`* {.preservesOr.} = ref object
|
||||
case orKind*: DefinitionKind
|
||||
of DefinitionKind.`or`:
|
||||
`or`*: DefinitionOr
|
||||
|
||||
of DefinitionKind.`and`:
|
||||
`and`*: DefinitionAnd
|
||||
|
||||
of DefinitionKind.`Pattern`:
|
||||
`pattern`*: Pattern
|
||||
|
||||
|
||||
NamedAlternative* {.preservesTuple.} = object
|
||||
`variantLabel`*: string
|
||||
`pattern`*: Pattern
|
||||
|
||||
SchemaData* {.preservesDictionary.} = object
|
||||
`embeddedType`*: EmbeddedTypeName
|
||||
`version`* {.preservesLiteral: "1".}: bool
|
||||
`definitions`*: Definitions
|
||||
|
||||
Schema* {.preservesRecord: "schema".} = object
|
||||
`data`*: SchemaData
|
||||
|
||||
PatternKind* {.pure.} = enum
|
||||
`SimplePattern`, `CompoundPattern`
|
||||
`Pattern`* {.preservesOr.} = ref object
|
||||
case orKind*: PatternKind
|
||||
of PatternKind.`SimplePattern`:
|
||||
`simplepattern`*: SimplePattern
|
||||
|
||||
of PatternKind.`CompoundPattern`:
|
||||
`compoundpattern`*: CompoundPattern
|
||||
|
||||
|
||||
Binding* {.preservesRecord: "named".} = object
|
||||
`name`* {.preservesSymbol.}: string
|
||||
`pattern`*: SimplePattern
|
||||
|
||||
proc `$`*(x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
|
||||
EmbeddedTypeName |
|
||||
Definitions |
|
||||
DictionaryEntries |
|
||||
NamedPattern |
|
||||
SimplePattern |
|
||||
NamedSimplePattern |
|
||||
Definition |
|
||||
NamedAlternative |
|
||||
Schema |
|
||||
Pattern |
|
||||
Binding): string =
|
||||
`$`(toPreserve(x))
|
||||
|
||||
proc encode*[E](x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
|
||||
EmbeddedTypeName |
|
||||
Definitions |
|
||||
DictionaryEntries |
|
||||
NamedPattern |
|
||||
SimplePattern |
|
||||
NamedSimplePattern |
|
||||
Definition |
|
||||
NamedAlternative |
|
||||
Schema |
|
||||
Pattern |
|
||||
Binding): seq[byte] =
|
||||
encode(toPreserve(x, E))
|
|
@ -1,450 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
## NPeg parser for Preserves Schemas.
|
||||
## https://preserves.gitlab.io/preserves/preserves-schema.html
|
||||
|
||||
import std/[parseutils, macros, strutils, tables]
|
||||
from os import absolutePath, isAbsolute, parentDir
|
||||
|
||||
import npeg
|
||||
import ../preserves, ./parse, ./pegs
|
||||
|
||||
type
|
||||
Value = Preserve[void]
|
||||
|
||||
Stack = seq[tuple[node: SchemaNode, pos: int]]
|
||||
|
||||
SchemaNodeKind* = enum
|
||||
# TODO: audit these kinds later
|
||||
snkOr,
|
||||
snkAnd,
|
||||
snkAlt, # same as snkNamed?
|
||||
snkAny,
|
||||
snkAtom,
|
||||
snkEmbedded,
|
||||
snkLiteral,
|
||||
snkSequenceOf
|
||||
snkSetOf,
|
||||
snkDictOf,
|
||||
snkRecord,
|
||||
snkTuple,
|
||||
snkVariableTuple,
|
||||
snkDictionary,
|
||||
snkNamed,
|
||||
snkRef
|
||||
|
||||
AtomKind* = enum
|
||||
akBool = "bool"
|
||||
akFloat = "float"
|
||||
akDouble = "double"
|
||||
akInt = "int"
|
||||
akString = "string"
|
||||
akBytes = "bytes"
|
||||
akSymbol = "symbol"
|
||||
|
||||
SchemaNode* {.acyclic.} = ref object
|
||||
case kind*: SchemaNodeKind
|
||||
of snkAlt:
|
||||
altLabel*: string
|
||||
altBranch*: SchemaNode
|
||||
of snkAny: discard
|
||||
of snkAtom:
|
||||
atom*: AtomKind
|
||||
of snkEmbedded:
|
||||
embed*: SchemaNode
|
||||
of snkLiteral:
|
||||
value*: Value
|
||||
of snkNamed:
|
||||
name*: string
|
||||
pattern*: SchemaNode
|
||||
of snkRef:
|
||||
refPath*: seq[string]
|
||||
else:
|
||||
nodes*: seq[SchemaNode]
|
||||
|
||||
Schema* = ref object
|
||||
version*: int
|
||||
embeddedType*: string
|
||||
definitions*: OrderedTable[string, SchemaNode]
|
||||
|
||||
ParseState = object
|
||||
filepath: string
|
||||
stack: Stack
|
||||
schema: Schema
|
||||
|
||||
proc add(a: SchemaNode; b: SchemaNode|seq[SchemaNode]): SchemaNode {.discardable.} =
|
||||
a.nodes.add b
|
||||
a
|
||||
|
||||
iterator items*(sn: SchemaNode): SchemaNode =
|
||||
case sn.kind
|
||||
of snkAny, snkAtom, snkLiteral, snkRef: discard
|
||||
of snkAlt:
|
||||
yield sn.altBranch
|
||||
of snkEmbedded:
|
||||
yield sn.embed
|
||||
of snkNamed:
|
||||
yield sn.pattern
|
||||
else:
|
||||
for i in 0..sn.nodes.high:
|
||||
yield sn.nodes[i]
|
||||
|
||||
proc `$`*(n: SchemaNode): string =
|
||||
case n.kind
|
||||
of snkOr:
|
||||
result.add "/ "
|
||||
result.add join(n.nodes, " / ")
|
||||
of snkAnd:
|
||||
result.add "& "
|
||||
result.add join(n.nodes, " & ")
|
||||
of snkAlt:
|
||||
case n.altBranch.kind
|
||||
of snkRecord, snkRef:
|
||||
result.add $n.altBranch
|
||||
of snkLiteral:
|
||||
result.add '='
|
||||
result.add $n.altBranch
|
||||
else:
|
||||
result.add '@'
|
||||
result.add n.altLabel
|
||||
result.add ' '
|
||||
result.add $n.altBranch
|
||||
of snkAny: result.add "any"
|
||||
of snkAtom: result.add $n.atom
|
||||
of snkEmbedded:
|
||||
result.add "#!" & $n.embed
|
||||
of snkLiteral:
|
||||
case n.value.kind
|
||||
of pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString:
|
||||
result.add $n.value
|
||||
else:
|
||||
result.add "<<lit>" & $n.value & ">"
|
||||
of snkSequenceOf:
|
||||
result.add "[ "
|
||||
result.add $n.nodes[0]
|
||||
result.add " ... ]"
|
||||
of snkSetOf:
|
||||
result.add "#{"
|
||||
result.add n.nodes.join(" ")
|
||||
result.add '}'
|
||||
of snkDictOf:
|
||||
result.add '{'
|
||||
result.add $n.nodes[0]
|
||||
result.add " : "
|
||||
result.add $n.nodes[1]
|
||||
result.add " ...:...}"
|
||||
of snkRecord:
|
||||
result.add '<'
|
||||
if n.nodes[0].kind == snkLiteral and n.nodes[0].value.kind == pkSymbol:
|
||||
result.add n.nodes[0].value.symbol
|
||||
for i in 1..n.nodes.high:
|
||||
result.add ' '
|
||||
result.add $n.nodes[i]
|
||||
else:
|
||||
result.add join(n.nodes, " ")
|
||||
result.add '>'
|
||||
of snkTuple:
|
||||
result.add '['
|
||||
result.add join(n.nodes, " ")
|
||||
result.add ']'
|
||||
of snkVariableTuple:
|
||||
result.add '['
|
||||
result.add join(n.nodes, " ")
|
||||
result.add " ...]"
|
||||
of snkDictionary:
|
||||
result.add '{'
|
||||
for i in countup(0, n.nodes.high, 2):
|
||||
result.add $n.nodes[i]
|
||||
result.add ": "
|
||||
result.add $n.nodes[i.succ]
|
||||
result.add ' '
|
||||
result.add '}'
|
||||
of snkNamed:
|
||||
result.add '@'
|
||||
result.add n.name
|
||||
result.add ' '
|
||||
result.add $n.pattern
|
||||
of snkRef:
|
||||
result.add join(n.refPath, ".")
|
||||
|
||||
proc `$`*(scm: Schema): string =
|
||||
result.add("version = $1 .\n" % $scm.version)
|
||||
if scm.embeddedType != "":
|
||||
result.add("EmbeddedTypeName = $1 .\n" % scm.embeddedType)
|
||||
for n, d in scm.definitions.pairs:
|
||||
result.add("$1 = $2 .\n" % [n, $d])
|
||||
|
||||
proc `$`(stack: Stack): string =
|
||||
for f in stack:
|
||||
result.add "\n"
|
||||
result.add $f.pos
|
||||
result.add ": "
|
||||
result.add $f.node
|
||||
|
||||
proc match(text: string; p: var ParseState) {.gcsafe.}
|
||||
|
||||
template newSchemaNode(snk: SchemaNodeKind): SchemaNode =
|
||||
SchemaNode(kind: snk)
|
||||
|
||||
template takeStackAt(): seq[SchemaNode] =
|
||||
var nodes = newSeq[SchemaNode]()
|
||||
let pos = capture[0].si
|
||||
var i: int
|
||||
while i < p.stack.len and p.stack[i].pos < pos:
|
||||
inc i
|
||||
let stop = i
|
||||
while i < p.stack.len:
|
||||
nodes.add(move p.stack[i].node)
|
||||
inc i
|
||||
p.stack.setLen(stop)
|
||||
nodes
|
||||
|
||||
template takeStackAfter(): seq[SchemaNode] =
|
||||
var nodes = newSeq[SchemaNode]()
|
||||
let pos = capture[0].si
|
||||
var i: int
|
||||
while i < p.stack.len and p.stack[i].pos <= pos:
|
||||
inc i
|
||||
let stop = i
|
||||
while i < p.stack.len:
|
||||
nodes.add(move p.stack[i].node)
|
||||
inc i
|
||||
p.stack.setLen(stop)
|
||||
nodes
|
||||
|
||||
template popStack(): SchemaNode =
|
||||
assert(p.stack.len > 0, capture[0].s)
|
||||
assert(capture[0].si <= p.stack[p.stack.high].pos, capture[0].s)
|
||||
p.stack.pop.node
|
||||
|
||||
template pushStack(n: SchemaNode) =
|
||||
let pos = capture[0].si
|
||||
var i: int
|
||||
while i < p.stack.len and p.stack[i].pos < pos:
|
||||
inc i
|
||||
p.stack.setLen(i)
|
||||
p.stack.add((n, pos))
|
||||
assert(p.stack.len > 0, capture[0].s)
|
||||
|
||||
const parser = peg("Schema", p: ParseState):
|
||||
|
||||
Schema <- ?editorCruft * S * +(Clause * S) * !1
|
||||
|
||||
Clause <- (Version | EmbeddedTypeName | Include | Definition) * S * '.'
|
||||
|
||||
Version <- "version" * S * >(*Digit):
|
||||
discard parseInt($1, p.schema.version)
|
||||
if p.schema.version != 1: fail()
|
||||
|
||||
EmbeddedTypeName <- "embeddedType" * S * >("#f" | Ref):
|
||||
if p.schema.embeddedType != "": fail()
|
||||
if $1 != "#f": p.schema.embeddedType = $1
|
||||
|
||||
Include <- "include" * S * (>(+Alnum) | ('"' * >(@'"'))):
|
||||
var ip = ParseState(
|
||||
schema: p.schema,
|
||||
filepath:
|
||||
if isAbsolute($1): $1
|
||||
else: absolutePath($1, p.filepath.parentDir))
|
||||
ip.filePath.setLen(ip.filePath.high)
|
||||
match(readFile ip.filepath, ip)
|
||||
|
||||
Definition <- >id * S * '=' * S * (OrPattern | AndPattern | Pattern):
|
||||
if p.schema.definitions.hasKey $1:
|
||||
raise newException(ValueError, "duplicate definition of " & $1)
|
||||
p.schema.definitions[$1] = popStack()
|
||||
p.stack.setLen(0)
|
||||
|
||||
OrPattern <- ?('/' * S) * AltPattern * +(S * '/' * S * AltPattern):
|
||||
let n = snkOr.newSchemaNode.add(takeStackAt())
|
||||
assert(n.nodes[0].kind == snkAlt, $n.nodes[0])
|
||||
pushStack n
|
||||
|
||||
AltPattern <-
|
||||
AltNamed |
|
||||
AltRecord |
|
||||
AltRef |
|
||||
AltLiteralPattern
|
||||
|
||||
AltNamed <- '@' * >id * S * Pattern:
|
||||
let n = SchemaNode(kind: snkAlt, altLabel: $1, altBranch: popStack())
|
||||
pushStack n
|
||||
|
||||
AltRecord <- '<' * >id * *(S * NamedPattern) * '>':
|
||||
let
|
||||
id = SchemaNode(kind: snkLiteral, value: symbol[void]($1))
|
||||
n = SchemaNode(kind: snkAlt,
|
||||
altLabel: $1,
|
||||
altBranch: snkRecord.newSchemaNode.add(id).add(takeStackAt()))
|
||||
pushStack n
|
||||
|
||||
AltRef <- Ref:
|
||||
let n = SchemaNode(kind: snkAlt, altLabel: $0, altBranch: popStack())
|
||||
pushStack n
|
||||
|
||||
AltLiteralPattern <-
|
||||
>Preserves.Boolean |
|
||||
>Preserves.Float |
|
||||
>Preserves.Double |
|
||||
>Preserves.SignedInteger |
|
||||
>Preserves.String |
|
||||
'=' * >Preserves.Symbol:
|
||||
let
|
||||
branch = SchemaNode(kind: snkLiteral, value: parsePreserves($1))
|
||||
label = case branch.value.kind
|
||||
of pkBoolean:
|
||||
if branch.value.bool: "true" else: "false"
|
||||
else: $branch.value
|
||||
# TODO: numbers?
|
||||
pushStack SchemaNode(kind: snkAlt, altLabel: label, altBranch: branch)
|
||||
|
||||
AndPattern <- ?('&' * S) * NamedPattern * +(S * '&' * S * NamedPattern)
|
||||
|
||||
Pattern <- SimplePattern | CompoundPattern
|
||||
|
||||
SimplePattern <-
|
||||
AnyPattern |
|
||||
AtomKindPattern |
|
||||
EmbeddedPattern |
|
||||
LiteralPattern |
|
||||
SequenceOfPattern |
|
||||
SetOfPattern |
|
||||
DictOfPattern |
|
||||
Ref
|
||||
|
||||
AnyPattern <- "any":
|
||||
let n = SchemaNode(kind: snkAny)
|
||||
pushStack n
|
||||
|
||||
AtomKindPattern <-
|
||||
"bool" |
|
||||
"float" |
|
||||
"double" |
|
||||
"int" |
|
||||
"string" |
|
||||
"bytes" |
|
||||
"symbol":
|
||||
let n = SchemaNode(kind: snkAtom)
|
||||
case $0
|
||||
of "bool": n.atom = akBool
|
||||
of "float": n.atom = akFloat
|
||||
of "double": n.atom = akDouble
|
||||
of "int": n.atom = akInt
|
||||
of "string": n.atom = akString
|
||||
of "bytes": n.atom = akBytes
|
||||
of "symbol": n.atom = akSymbol
|
||||
pushStack n
|
||||
|
||||
EmbeddedPattern <- "#!" * SimplePattern:
|
||||
let n = SchemaNode(kind: snkEmbedded, embed: popStack())
|
||||
pushStack n
|
||||
|
||||
LiteralPattern <- ('=' * >symbol) | ("<<lit>" * >Preserves.Value * ">") | >nonSymbolAtom:
|
||||
let n = SchemaNode(
|
||||
kind: snkLiteral,
|
||||
value: parsePreserves($1))
|
||||
pushStack n
|
||||
|
||||
SequenceOfPattern <- '[' * S * SimplePattern * S * "..." * S * ']':
|
||||
let n = newSchemaNode(snkSequenceOf).add(takeStackAfter())
|
||||
pushStack n
|
||||
|
||||
SetOfPattern <- "#{" * SimplePattern * '}'
|
||||
|
||||
DictOfPattern <-
|
||||
'{' *
|
||||
S * SimplePattern * S * ':' * S * SimplePattern * S * "...:..." * S *
|
||||
'}':
|
||||
let n = newSchemaNode(snkDictOf).add(takeStackAfter())
|
||||
assert(n.nodes.len == 2, $n.nodes)
|
||||
pushStack n
|
||||
|
||||
Ref <- >(Alpha * *Alnum) * *('.' * >(*Alnum)):
|
||||
let n = SchemaNode(kind: snkRef)
|
||||
for i in 1..<capture.len: n.refPath.add capture[i].s
|
||||
pushStack n
|
||||
|
||||
CompoundPattern <-
|
||||
RecordPattern |
|
||||
TuplePattern |
|
||||
VariableTuplePattern |
|
||||
DictionaryPattern
|
||||
|
||||
RecordPattern <-
|
||||
("<<rec>" * S * NamedPattern * *(S * NamedPattern) * '>') |
|
||||
('<' * >Value * *(S * NamedPattern) * '>'):
|
||||
let n = newSchemaNode(snkRecord).add(takeStackAfter())
|
||||
pushStack n
|
||||
|
||||
TuplePattern <-
|
||||
'[' * S * *(NamedPattern * S) * ']':
|
||||
var n = SchemaNode(kind: snkTuple)
|
||||
for frame in p.stack.mitems:
|
||||
if frame.pos > capture[0].si:
|
||||
n.nodes.add(move frame.node)
|
||||
pushStack n
|
||||
|
||||
VariableTuplePattern <-
|
||||
'[' * S * *(NamedPattern * S) * ?(Pattern * S) * "..." * S * ']':
|
||||
var n = SchemaNode(kind: snkVariableTuple)
|
||||
for frame in p.stack.mitems:
|
||||
if frame.pos > capture[0].si:
|
||||
n.nodes.add(move frame.node)
|
||||
pushStack n
|
||||
|
||||
DictionaryPattern <- '{' * S * *(Value * S * ':' * S * NamedSimplePattern * S) * '}':
|
||||
var n = SchemaNode(kind: snkDictionary)
|
||||
for frame in p.stack.mitems:
|
||||
if frame.pos > capture[0].si:
|
||||
n.nodes.add(move frame.node)
|
||||
pushStack n
|
||||
|
||||
NamedPattern <- ('@' * >id * S * SimplePattern) | Pattern:
|
||||
if capture.len == 2:
|
||||
var n = SchemaNode(
|
||||
kind: snkNamed, name: $1, pattern: popStack())
|
||||
pushStack n
|
||||
|
||||
NamedSimplePattern <- ('@' * >id * S * SimplePattern) | SimplePattern:
|
||||
if capture.len == 2:
|
||||
var n = SchemaNode(
|
||||
kind: snkNamed, name: $1, pattern: popStack())
|
||||
pushStack n
|
||||
|
||||
id <- Alpha * *Alnum
|
||||
|
||||
Comment <- ';' * @'\n'
|
||||
|
||||
S <- *(Space | Comment)
|
||||
|
||||
symbol <- Preserves.Symbol
|
||||
|
||||
nonSymbolAtom <-
|
||||
Preserves.Boolean |
|
||||
Preserves.Float |
|
||||
Preserves.Double |
|
||||
Preserves.SignedInteger |
|
||||
Preserves.String |
|
||||
Preserves.ByteString
|
||||
|
||||
Value <- Preserves.Value:
|
||||
pushStack SchemaNode(
|
||||
kind: snkLiteral,
|
||||
value: parsePreserves($0))
|
||||
|
||||
editorCruft <- '@' * @'\n'
|
||||
|
||||
proc match(text: string; p: var ParseState) =
|
||||
let match = parser.match(text, p)
|
||||
if not match.ok:
|
||||
raise newException(ValueError, "failed to parse " & p.filepath & ":\n" & text[0..<match.matchMax])
|
||||
|
||||
proc parsePreservesSchema*(text, filepath: string): Schema =
|
||||
## Parse a Preserves schema into an abstract syntax tree represented as a `Preserve`.
|
||||
var p = ParseState(filepath: filepath)
|
||||
new p.schema
|
||||
match(text, p)
|
||||
result = p.schema
|
||||
if result.version != 1:
|
||||
raise newException(ValueError, "missing or invalid Preserves schema version")
|
|
@ -6,26 +6,26 @@ import bigints, preserves
|
|||
|
||||
suite "conversions":
|
||||
test "dictionary":
|
||||
type Bar = object
|
||||
type Bar = tuple
|
||||
s: string
|
||||
type Foobar = tuple
|
||||
type Foobar {.preservesDictionary.} = object
|
||||
a, b: int
|
||||
c: Bar
|
||||
let
|
||||
c: Foobar = (a: 1, b: 2, c: Bar(s: "ku", ))
|
||||
c = Foobar(a: 1, b: 2, c: ("ku", ))
|
||||
b = toPreserve(c)
|
||||
a = preserveTo(b, Foobar)
|
||||
check(a.isSome and (get(a) == c))
|
||||
check(b.kind == pkDictionary)
|
||||
|
||||
test "records":
|
||||
type Bar {.record: "bar".} = object
|
||||
type Bar {.preservesRecord: "bar".} = object
|
||||
s: string
|
||||
type Foobar {.record: "foo".} = tuple
|
||||
type Foobar {.preservesRecord: "foo".} = object
|
||||
a, b: int
|
||||
c: Bar
|
||||
let
|
||||
tup: Foobar = (a: 1, b: 2, c: Bar(s: "ku", ))
|
||||
tup = Foobar(a: 1, b: 2, c: Bar(s: "ku", ))
|
||||
prs = toPreserve(tup)
|
||||
check(prs.kind == pkRecord)
|
||||
check($prs == """<foo 1 2 <bar "ku">>""")
|
||||
|
@ -35,7 +35,7 @@ suite "conversions":
|
|||
var a: Table[int, string]
|
||||
for i, s in ["a", "b", "c"]: a[i] = s
|
||||
let b = toPreserve(a)
|
||||
check($b == """{0: "a" 1: "b" 2: "c"}""")
|
||||
check($b == """{0: "a", 1: "b", 2: "c"}""")
|
||||
var c: Table[int, string]
|
||||
check(fromPreserve(c, b))
|
||||
check(a == c)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[tables, options, os, unittest]
|
||||
import preserves, preserves/parse, preserves/schema
|
||||
|
||||
suite "schema":
|
||||
|
||||
const
|
||||
binPath = "upstream/schema/schema.bin"
|
||||
|
||||
test "convertability":
|
||||
if not fileExists(binPath): skip()
|
||||
else:
|
||||
var
|
||||
b = decodePreserves readFile(binPath)
|
||||
scm = preserveTo(b, Schema)
|
||||
check scm.isSome
|
||||
if scm.isSome:
|
||||
var a = toPreserve(get scm)
|
||||
check(a == b)
|
|
@ -0,0 +1 @@
|
|||
Subproject commit b2c3032e7a9c5157aaea88a77be83438b7a23c58
|
Loading…
Reference in New Issue