Compare commits

...

2 Commits

Author SHA1 Message Date
Emery Hemingway 2dd63903f0 Remove records module
Redundant with toPreserve and fromPreserve.
2021-09-25 13:57:58 +02:00
Emery Hemingway 42a9b26458 Embeddable Preserves
Make Preserve a generic type that can embed a native Nim type.
Generate generic implementations from schemas and discard
embeddedType.
2021-09-25 13:47:05 +02:00
7 changed files with 464 additions and 363 deletions

View File

@ -10,16 +10,14 @@ from std/macros import hasCustomPragma, getCustomPragmaVal
type
PreserveKind* = enum
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol,
pkRecord, pkSequence, pkSet, pkDictionary
pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
const
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol}
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
type
DictEntry = tuple[key: Preserve, val: Preserve]
Preserve* {.acyclic.} = object
Preserve*[E = void] {.acyclic.} = object
case kind*: PreserveKind
of pkBoolean:
bool*: bool
@ -38,18 +36,24 @@ type
of pkSymbol:
symbol*: string
of pkRecord:
record*: seq[Preserve] # label is last
record*: seq[Preserve[E]] # label is last
of pkSequence:
sequence*: seq[Preserve]
sequence*: seq[Preserve[E]]
of pkSet:
set*: seq[Preserve]
set*: seq[Preserve[E]]
# TODO: HashSet
of pkDictionary:
dict*: seq[DictEntry]
dict*: seq[DictEntry[E]]
# TODO: Tables
of pkEmbedded:
embed*: E
embedded*: bool
## Flag to mark embedded Preserves
proc `==`*(x, y: Preserve): bool =
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:
case x.kind
@ -79,6 +83,12 @@ proc `==`*(x, y: Preserve): bool =
result = x.set == y.set
of pkDictionary:
result = x.dict == y.dict
of pkEmbedded:
when A is B:
when A is void:
result = true
else:
result = x.embed == y.embed
proc `<`(x, y: string | seq[byte]): bool =
for i in 0 .. min(x.high, y.high):
@ -86,7 +96,7 @@ proc `<`(x, y: string | seq[byte]): bool =
if x[i] != y[i]: return false
x.len < y.len
proc `<`*(x, y: Preserve): bool =
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
## Preserves have a total order over Values. Check if `x` is ordered before `y`.
if x.embedded != y.embedded:
result = y.embedded
@ -138,6 +148,9 @@ proc `<`*(x, y: Preserve): bool =
if x.dict[i].val < y.dict[i].val: return true
if x.dict[i].val != y.dict[i].val: return false
result = x.dict.len < y.dict.len
of pkEmbedded:
when (not A is void) and (A is B):
result = x.embed < y.embed
proc hash*(pr: Preserve): Hash =
## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`.
@ -172,6 +185,8 @@ proc hash*(pr: Preserve): Hash =
of pkDictionary:
for (key, val) in pr.dict.items:
h = h !& hash(key) !& hash(val)
of pkEmbedded:
h = h !& hash(pr.embed)
!$h
proc `[]`*(pr: Preserve; i: int): Preserve =
@ -224,39 +239,37 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
return
pr.dict.add((key, val, ))
proc symbol*(s: string; E = void): Preserve {.inline.} =
proc symbol*[E](s: string): Preserve[E] {.inline.} =
## Create a Preserves symbol value.
Preserve(kind: pkSymbol, symbol: s)
Preserve[E](kind: pkSymbol, symbol: s)
proc initRecord*(label: Preserve; arity = 0): Preserve =
proc initRecord*[E](label: Preserve[E]; arity: Natural = 0): Preserve[E] =
## Create a Preserves record value.
result = Preserve(kind: pkRecord, record: newSeq[Preserve](arity.succ))
result = Preserve[E](kind: pkRecord, record: newSeq[Preserve[E]](arity.succ))
result.record[arity] = label
proc initRecord*(label: Preserve; args: varargs[Preserve]): Preserve =
proc initRecord*[E](label: Preserve[E]; args: varargs[Preserve[E]]): Preserve[E] =
## Create a Preserves record value.
result = Preserve(kind: pkRecord,
record: newSeqOfCap[Preserve](1+args.len))
result = Preserve[E](kind: pkRecord,
record: newSeqOfCap[Preserve[E]](1+args.len))
for arg in args:
result.record.add(arg)
result.record.add(label)
proc initRecord*(label: string; args: varargs[Preserve, toPreserve]): Preserve =
## Convert ``label`` to a symbol and create a new record.
runnableExamples:
assert($initRecord("foo", 1, 2.0) == "<foo 1 2.0f>")
initRecord(symbol(label), args)
proc initSequence*(len = 0): Preserve =
proc initSequence*[E](len: Natural = 0): Preserve[E] =
## Create a Preserves sequence value.
Preserve(kind: pkSequence, record: newSeq[Preserve](len))
Preserve[E](kind: pkSequence, sequence: newSeq[Preserve[E]](len))
proc initSet*(): Preserve = Preserve(kind: pkSet)
proc initSet*[E](): Preserve[E] = Preserve[E](kind: pkSet)
## Create a Preserves set value.
proc initDictionary*(): Preserve = Preserve(kind: pkDictionary)
proc initDictionary*[E](): Preserve[E] = Preserve[E](kind: pkDictionary)
## Create a Preserves dictionary value.
proc embed*[E](e: E): Preserve[E] =
## Create a Preserves value that embeds ``e``.
Preserve[E](kind: pkEmbedded, embed: e)
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
@ -327,8 +340,10 @@ proc isSet*(pr: Preserve): bool {.inline.} = pr.kind == pkSet
proc isDictionary*(pr: Preserve): bool {.inline.} = pr.kind == pkDictionary
## Check if ``pr`` is a Preserves dictionary.
func isEmbedded*(pr: Preserve): bool {.inline.} = pr.embedded
func isEmbedded*[E](pr: Preserve[E]): bool {.inline.} =
## Check if ``pr`` is an embedded value.
when E is void: pr.embedded # embedded Preserves
else: pr.kind == pkEmbedded # embedded Nim
proc label*(pr: Preserve): Preserve {.inline.} =
## Return the label of record value.
@ -366,7 +381,7 @@ proc readVarint(s: Stream): int =
break
shift.inc 7
proc write*(str: Stream; pr: Preserve) =
proc write*[E](str: Stream; pr: Preserve[E]) =
## Write the binary-encoding of a Preserves value to a stream.
if pr.embedded: str.write(0x86'u8)
case pr.kind:
@ -460,15 +475,19 @@ proc write*(str: Stream; pr: Preserve) =
str.write(key)
str.write(value)
str.write(0x84'u8)
of pkEmbedded:
when not E is void:
str.write(0x86'u8)
str.write(pr.embed.toPreserve)
proc encode*(pr: Preserve): seq[byte] =
proc encode*[E](pr: Preserve[E]): seq[byte] =
## Return the binary-encoding of a Preserves value.
let s = newStringStream()
s.write pr
s.setPosition 0
result = cast[seq[byte]](s.readAll)
proc decodePreserves*(s: Stream): Preserve =
proc decodePreserves*(s: Stream; E = void): Preserve[E] =
## Decode a Preserves value from a binary-encoded stream.
proc assertStream(check: bool) =
if not check:
@ -476,61 +495,61 @@ proc decodePreserves*(s: Stream): Preserve =
const endMarker = 0x84
let tag = s.readUint8()
case tag
of 0x80: result = Preserve(kind: pkBoolean, bool: false)
of 0x81: result = Preserve(kind: pkBoolean, bool: true)
of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
of 0x82:
when system.cpuEndian == bigEndian:
result = Preserve(kind: pkFloat, float: s.readFloat32())
result = Preserve[E](kind: pkFloat, float: s.readFloat32())
else:
result = Preserve(kind: pkFloat)
result = Preserve[E](kind: pkFloat)
var be = s.readFloat32()
swapEndian32(result.float.addr, be.addr)
of 0x83:
when system.cpuEndian == bigEndian:
result = Preserve(kind: pkDouble, double: s.readFloat64())
result = Preserve[E](kind: pkDouble, double: s.readFloat64())
else:
result = Preserve(kind: pkDouble)
result = Preserve[E](kind: pkDouble)
var be = s.readFloat64()
swapEndian64(result.double.addr, be.addr)
of 0x86:
result = decodePreserves(s)
result = decodePreserves(s, E)
result.embedded = true
of 0xb1:
result = Preserve(kind: pkString)
result = Preserve[E](kind: pkString)
let len = s.readVarint()
result.string = s.readStr(len)
of 0xb2:
result = Preserve(kind: pkByteString)
result = Preserve[E](kind: pkByteString)
let len = s.readVarint()
result.bytes = cast[seq[byte]](s.readStr(len))
of 0xb3:
let len = s.readVarint()
result = Preserve(kind: pkSymbol, symbol: s.readStr(len))
result = Preserve[E](kind: pkSymbol, symbol: s.readStr(len))
of 0xb4:
result = Preserve(kind: pkRecord)
var label = decodePreserves(s)
result = Preserve[E](kind: pkRecord)
var label = decodePreserves(s, E)
while s.peekUint8() != endMarker:
result.record.add decodePreserves(s)
result.record.add decodePreserves(s, E)
result.record.add(move label)
discard s.readUint8()
of 0xb5:
result = Preserve(kind: pkSequence)
result = Preserve[E](kind: pkSequence)
while s.peekUint8() != endMarker:
result.sequence.add decodePreserves(s)
result.sequence.add decodePreserves(s, E)
discard s.readUint8()
of 0xb6:
result = Preserve(kind: pkSet)
result = Preserve[E](kind: pkSet)
while s.peekUint8() != endMarker:
incl(result, decodePreserves(s))
incl(result, decodePreserves(s, E))
discard s.readUint8()
of 0xb7:
result = Preserve(kind: pkDictionary)
result = Preserve[E](kind: pkDictionary)
while s.peekUint8() != endMarker:
result[decodePreserves(s)] = decodePreserves(s)
result[decodePreserves(s, E)] = decodePreserves(s, E)
discard s.readUint8()
of 0xb0:
let len = s.readVarint()
result = Preserve(kind: pkBigInteger, bigint: initBigint 0)
result = Preserve[E](kind: pkBigInteger, bigint: initBigint 0)
for _ in 1..len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32
of endMarker:
@ -539,29 +558,29 @@ proc decodePreserves*(s: Stream): Preserve =
case 0xf0 and tag
of 0x90:
var n = tag.BiggestInt
result = Preserve(kind: pkSignedInteger,
result = Preserve[E](kind: pkSignedInteger,
int: n - (if n > 0x9c: 0xa0 else: 0x90))
of 0xa0:
let len = (tag.int and 0x0f) + 1
if len <= 8:
result = Preserve(kind: pkSignedInteger, int: s.readUint8().BiggestInt)
result = Preserve[E](kind: pkSignedInteger, int: s.readUint8().BiggestInt)
if (result.int and 0x80) != 0: result.int.dec(0x100)
for i in 1..<len:
result.int = (result.int shl 8) or s.readUint8().BiggestInt
else:
result = Preserve(kind: pkBigInteger)
result = Preserve[E](kind: pkBigInteger)
for i in 0..<len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32
else:
assertStream(false)
proc decodePreserves*(s: string): Preserve =
proc decodePreserves*(s: string; E = void): Preserve[E] =
## Decode a string of binary-encoded Preserves.
s.newStringStream.decodePreserves
decodePreserves(s.newStringStream, E)
proc decodePreserves*(s: seq[byte]): Preserve =
proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
## Decode a byte-string of binary-encoded Preserves.
cast[string](s).decodePreserves
decodePreserves(cast[string](s), E)
template record*(label: string) {.pragma.}
## Serialize this object or tuple as a record. See ``toPreserve``.
@ -569,61 +588,77 @@ template record*(label: string) {.pragma.}
template unpreservable*() {.pragma.}
## Pragma to forbid a type from being converted by ``toPreserve``.
proc toPreserve*[T](x: T): Preserve =
proc toPreserve*[T](x: T; E = void): Preserve[E] =
## Serializes ``x`` to Preserves. Can be customized by defining
## ``toPreserveHook(x: T)`` in the calling scope.
when (T is Preserve): result = x
elif compiles(toPreserveHook(x)):
result = toPreserveHook(x)
## ``toPreserveHook(x: T; E: typedesc)`` in the calling scope.
## Any ``toPreserveHook`` that does not compile will be discarded;
## *Write tests for your hooks!*
when (T is Preserve[E]): result = x
elif compiles(toPreserveHook(x, E)):
result = toPreserveHook(x, E)
elif T is Bigint:
result = Preserve(kind: pkBigInteger, bigint: x)
result = Preserve[E](kind: pkBigInteger, bigint: x)
elif T is seq[byte]:
result = Preserve(kind: pkByteString, bytes: x)
result = Preserve[E](kind: pkByteString, bytes: x)
elif T is array | seq:
result = Preserve(kind: pkSequence, sequence: newSeqOfCap[Preserve](x.len))
for v in x.items: result.sequence.add(toPreserve(v))
result = Preserve[E](kind: pkSequence, sequence: newSeqOfCap[Preserve[E]](x.len))
for v in x.items: result.sequence.add(toPreserve(v, E))
elif T is bool:
result = Preserve(kind: pkBoolean, bool: x)
result = Preserve[E](kind: pkBoolean, bool: x)
elif T is distinct:
result = toPreserve(x.distinctBase)
result = toPreserve(x.distinctBase, E)
elif T is float:
result = Preserve(kind: pkFloat, float: x)
result = Preserve[E](kind: pkFloat, float: x)
elif T is float64:
result = Preserve(kind: pkDouble, double: x)
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(kind: pkRecord)
for _, f in x.fieldPairs: result.record.add(toPreserve(f))
result.record.add(symbol(T.getCustomPragmaVal(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(kind: pkDictionary)
result = Preserve[E](kind: pkDictionary)
for k, v in x.fieldPairs:
result[symbol(k)] = toPreserve(v)
result[symbol[E](k)] = toPreserve(v, E)
elif T is Ordinal:
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt)
result = Preserve[E](kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref:
if system.`==`(x, nil): result = symbol("null")
else: result = toPreserve(x[])
if system.`==`(x, nil): result = symbol[E]("null")
else: result = toPreserve(x[], E)
elif T is string:
result = Preserve(kind: pkString, string: x)
result = Preserve[E](kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt)
result = Preserve[E](kind: pkSignedInteger, int: x.BiggestInt)
else:
raiseAssert("unpreservable type" & $T)
proc toPreserveHook*[T](set: HashSet[T]): Preserve =
proc toPreserveHook*[A](pr: Preserve[A]; E: typedesc): Preserve[E] =
## Hook for converting ``Preserve`` values with different embedded types.
if pr.kind == pkEmbedded:
when E is void:
result = toPreserve(pr.embed, E)
else:
result = Preserve[E](pk: pr.kind, embed: (E)pr.embed)
else:
result = cast[Preserve[E]](pr)
proc toPreserveHook*[T](set: HashSet[T]; E: typedesc): Preserve[E] =
## Hook for preserving ``HashSet``.
Preserve(kind: pkSet, set: set.map(toPreserve))
result = Preserve[E](kind: pkSet, set: newSeqOfCap[Preserve[E]](set.len))
for e in set: result.incl toPreserve(e, E)
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B]): Preserve =
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B], E: typedesc): Preserve[E] =
## Hook for preserving ``Table``.
result = initDictionary()
for k, v in table.pairs: result[k.toPreserve] = v.toPreserve
result = initDictionary[E]()
for k, v in table.pairs: result[toPreserve(k, E)] = toPreserve(v, E)
proc fromPreserve*[T](v: var T; pr: Preserve): bool =
proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
## Inplace version of `preserveTo`. Returns ``true`` on
## a complete match, otherwise returns ``false``.
## Can be customized with `fromPreserveHook[E](x: T; var pr: Preserve[E]): bool`.
## Any ``fromPreserveHook`` that does not compile will be discarded;
## *Write tests for your hooks!*
# TODO: {.raises: [].}
runnableExamples:
import preserves, preserves/parse
@ -637,6 +672,8 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
when T is Value:
v = pr
result = true
elif T is E:
result = pr.embed
elif compiles(fromPreserveHook(v, pr)):
result = fromPreserveHook(v, pr)
elif T is Bigint:
@ -695,7 +732,7 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
for key, val in v.fieldPairs:
inc fieldCount
for (pk, pv) in pr.dict.items:
var sym = symbol(key)
var sym = symbol[E](key)
if sym == pk:
result = result and fromPreserve(val, pv)
break
@ -706,7 +743,7 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
v = (T)pr.int
result = true
elif T is ref:
if pr != symbol("null"):
if pr != symbol[E]("null"):
new v
result = fromPreserve(v[], pr)
elif T is string:
@ -735,13 +772,28 @@ proc preserveTo*(pr: Preserve; T: typedesc): Option[T] =
if fromPreserve(v, pr):
result = some(move v)
proc fromPreserveHook*[A,B,E](t: var Table[A,B]|TableRef[A,B]; pr: Preserve): bool =
if pr.isDictionary:
for k, v in pr.pairs:
t[preserveTo(k, A)] = preserveTo(v, B)
result = true
proc fromPreserveHook*[T, E](set: var HashSet[T]; pr: Preserve[E]): bool =
## Hook for preserving ``HashSet``.
if pr.kind == pkSet:
result = true
set.init(pr.set.len)
var e: T
for pe in pr.set:
result = fromPreserve(e, pe)
if not result: break
set.incl(move e)
proc concat(result: var string; pr: Preserve) =
proc fromPreserveHook*[A,B,E](t: var (Table[A,B]|TableRef[A,B]); pr: Preserve[E]): bool =
if pr.isDictionary:
result = true
var a: A
var b: B
for (k, v) in pr.dict:
result = fromPreserve(a, k) and fromPreserve(b, v)
if not result: break
t[move a] = move b
proc concat[E](result: var string; pr: Preserve[E]) =
if pr.embedded: result.add("#!")
case pr.kind:
of pkBoolean:
@ -804,6 +856,20 @@ proc concat(result: var string; pr: Preserve) =
result.concat(value)
inc i
result.add('}')
of pkEmbedded:
when not E is void:
result.add("#!")
result.add($pr.embed)
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)

View File

@ -4,31 +4,31 @@
import std/[json, tables]
import ../preserves
proc toPreserveHook*(js: JsonNode): Preserve =
proc toPreserveHook*(js: JsonNode; E: typedesc): Preserve[E] =
case js.kind
of JString:
result = Preserve(kind: pkString, string: js.str)
result = Preserve[E](kind: pkString, string: js.str)
of JInt:
result = Preserve(kind: pkSignedInteger, int: js.num)
result = Preserve[E](kind: pkSignedInteger, int: js.num)
of JFloat:
result = Preserve(kind: pkDouble, double: js.fnum)
result = Preserve[E](kind: pkDouble, double: js.fnum)
of JBool:
result = case js.bval
of false: symbol"false"
of true: symbol"true"
of false: symbol[E]"false"
of true: symbol[E]"true"
of JNull:
result = symbol"null"
result = symbol[E]"null"
of JObject:
result = Preserve(kind: pkDictionary)
result = Preserve[E](kind: pkDictionary)
for key, val in js.fields.pairs:
result[Preserve(kind: pkString, string: key)] = toPreserveHook(val)
result[Preserve[E](kind: pkString, string: key)] = toPreserveHook(val, E)
of JArray:
result = Preserve(kind: pkSequence,
sequence: newSeq[Preserve](js.elems.len))
result = Preserve[E](kind: pkSequence,
sequence: newSeq[Preserve[E]](js.elems.len))
for i, e in js.elems:
result.sequence[i] = toPreserveHook(e)
result.sequence[i] = toPreserveHook(e, E)
proc fromPreserveHook*(js: var JsonNode; prs: Preserve): bool =
proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
case prs.kind:
of pkBoolean:
js = newJBool(prs.bool)
@ -67,6 +67,16 @@ proc fromPreserveHook*(js: var JsonNode; prs: Preserve): bool =
else: return false
true
proc toJsonHook*(pr: Preserve): JsonNode =
if not fromPreserve(result, pr):
proc toJsonHook*[E](pr: Preserve[E]): JsonNode =
if not fromPreserveHook(result, pr):
raise newException(ValueError, "cannot convert Preserves value to JSON")
proc fromJsonHook*[E](pr: var Preserve[E]; js: JsonNode) =
pr = toPreserveHook(js, E)
when isMainModule:
var js = JsonNode()
var pr = js.toPreserveHook(void)
assert fromPreserveHook(js, pr)
fromJsonHook(pr, js)
js = toJsonHook(pr)

View File

@ -6,14 +6,15 @@ import npeg
import ../preserves, ./pegs
type
Frame = tuple[value: Preserve, pos: int]
Value = Preserve[void]
Frame = tuple[value: Value, pos: int]
Stack = seq[Frame]
proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n)
template pushStack(v: Preserve) = stack.add((v, capture[0].si))
template pushStack(v: Value) = stack.add((v, capture[0].si))
proc parsePreserves*(text: string): Preserve {.gcsafe.} =
proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
const pegParser = peg("Document", stack: Stack):
# Override rules from pegs.nim
@ -21,7 +22,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
Preserves.Record <- Preserves.Record:
var
record: seq[Preserve]
record: seq[Value]
labelOff: int
while stack[labelOff].pos < capture[0].si:
inc labelOff
@ -29,18 +30,18 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
record.add(move stack[i].value)
record.add(move stack[labelOff].value)
stack.shrink record.len
pushStack Preserve(kind: pkRecord, record: move record)
pushStack Value(kind: pkRecord, record: move record)
Preserves.Sequence <- Preserves.Sequence:
var sequence: seq[Preserve]
var sequence: seq[Value]
for frame in stack.mitems:
if frame.pos > capture[0].si:
sequence.add(move frame.value)
stack.shrink sequence.len
pushStack Preserve(kind: pkSequence, sequence: move sequence)
pushStack Value(kind: pkSequence, sequence: move sequence)
Preserves.Dictionary <- Preserves.Dictionary:
var prs = Preserve(kind: pkDictionary)
var prs = Value(kind: pkDictionary)
for i in countDown(stack.high.pred, 0, 2):
if stack[i].pos < capture[0].si: break
prs[move stack[i].value] = stack[i.succ].value
@ -48,7 +49,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
pushStack prs
Preserves.Set <- Preserves.Set:
var prs = Preserve(kind: pkSet)
var prs = Value(kind: pkSet)
for frame in stack.mitems:
if frame.pos > capture[0].si:
prs.incl(move frame.value)
@ -57,36 +58,36 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
Preserves.Boolean <- Preserves.Boolean:
case $0
of "#f": pushStack Preserve(kind: pkBoolean)
of "#t": pushStack Preserve(kind: pkBoolean, bool: true)
of "#f": pushStack Value(kind: pkBoolean)
of "#t": pushStack Value(kind: pkBoolean, bool: true)
else: discard
Preserves.Float <- Preserves.Float:
pushStack Preserve(kind: pkFloat, float: parseFloat($1))
pushStack Value(kind: pkFloat, float: parseFloat($1))
Preserves.Double <- Preserves.Double:
pushStack Preserve(kind: pkDouble)
pushStack Value(kind: pkDouble)
let i = stack.high
discard parseBiggestFloat($0, stack[i].value.double)
Preserves.SignedInteger <- Preserves.SignedInteger:
pushStack Preserve(kind: pkSignedInteger, int: parseInt($0))
pushStack Value(kind: pkSignedInteger, int: parseInt($0))
Preserves.String <- Preserves.String:
pushStack Preserve(kind: pkString, string: unescape($0))
pushStack Value(kind: pkString, string: unescape($0))
Preserves.charByteString <- Preserves.charByteString:
let s = unescape($1)
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s))
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](s))
Preserves.hexByteString <- Preserves.hexByteString:
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1)))
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1)))
Preserves.b64ByteString <- Preserves.b64ByteString:
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
Preserves.Symbol <- Preserves.Symbol:
pushStack Preserve(kind: pkSymbol, symbol: $0)
pushStack Value(kind: pkSymbol, symbol: $0)
Preserves.Embedded <- Preserves.Embedded:
var v = stack.pop.value
@ -94,7 +95,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
pushStack v
Preserves.Compact <- Preserves.Compact:
pushStack decodePreserves(stack.pop.value.bytes)
pushStack decodePreserves(stack.pop.value.bytes, void)
var stack: Stack
let match = pegParser.match(text, stack)

View File

@ -1,13 +1,16 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[os, strutils, tables]
import std/[os, strutils, sets, tables]
import compiler/[ast, idents, renderer, lineinfos]
import ../../preserves, ../schemas
type TypeTable = OrderedTable[string, PNode]
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
@ -52,31 +55,50 @@ proc ident(sn: SchemaNode): PNode =
raiseAssert("no ident for " & $sn.kind & " " & $sn)
s.ident.accQuote
proc typeIdent(sn: SchemaNode): PNode =
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:
case sn.atom
of akBool: ident"bool"
of akFloat: ident"float32"
of akDouble: ident"float64"
of akInt: ident"BiggestInt"
of akString: ident"string"
of akBytes: nn(nkBracketExpr, ident"seq", ident"byte")
of akSymbol: ident"string" # TODO: distinct string type for symbols?
of snkNamed:
sn.pattern.typeIdent
of snkAtom, snkLiteral: discard
of snkAlt:
result = isEmbeddable(scm, sn.altBranch, seen)
of snkAny: result = true
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
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:
ident"Preserve"
for bn in sn.nodes:
result = isEmbeddable(scm, bn, seen)
if result: break
proc toExport(n: sink PNode): PNode =
nkPostFix.newNode.add(ident"*", n)
proc newEmpty(): PNode = newNode(nkEmpty)
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
@ -86,7 +108,46 @@ proc isConst(scm: Schema; sn: SchemaNode): bool =
result = isConst(scm, scm.definitions[sn.refPath[0]])
else: discard
proc literal(scm: Schema; sn: SchemaNode): Preserve =
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:
@ -97,14 +158,6 @@ proc literal(scm: Schema; sn: SchemaNode): Preserve =
else:
raiseAssert("not convertable to a literal: " & $sn)
proc isSymbolEnum(sn: SchemaNode): bool =
if sn.kind == snkOr:
for bn in sn.nodes:
if bn.altBranch.kind != snkLiteral or
bn.altBranch.value.kind != pkSymbol:
return false
result = true
proc toEnumTy(sn: SchemaNode): PNode =
result = nkEnumTy.newNode.add newEmpty()
for bn in sn.nodes:
@ -118,7 +171,22 @@ proc toEnumDef(name: string; sn: SchemaNode): PNode =
newEmpty(),
sn.toEnumTy)
proc typeDef(sn: SchemaNode; name: string; ty: PNode): PNode =
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,
@ -128,22 +196,24 @@ proc typeDef(sn: SchemaNode; name: string; ty: PNode): PNode =
nn(nkExprColonExpr,
ident"record",
PNode(kind: nkStrLit, strVal: sn.nodes[0].value.symbol)))),
newEmpty(),
embeddingParams(embeddable),
ty)
else:
nn(nkTypeDef, name.ident.toExport, newEmpty(), ty)
nn(nkTypeDef, name.ident.toExport, embeddingParams(embeddable), ty)
proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PNode =
proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""):
TypeSpec =
if name in known: return known[name]
case sn.kind
of snkOr:
if sn.isSymbolEnum:
result = sn.toEnumTy
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)
known[enumName] = (toEnumDef(enumName, sn), false)
let recCase = nkRecCase.newNode.add(
nkIdentDefs.newNode.add(
"kind".ident.toExport,
@ -152,15 +222,19 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
for bn in sn.nodes:
assert(bn.kind == snkAlt, $bn.kind)
doAssert(name != "", " no name for " & $sn)
var memberTypeIdent: PNode
var memberType: TypeSpec
if bn.altbranch.kind == snkRef:
memberTypeIdent = bn.altBranch.typeIdent
memberType = typeIdent(scm, bn.altBranch)
else:
let memberTypeName = name & bn.altLabel.nimIdentNormalize
memberTypeIdent = ident memberTypeName
memberType.node = ident memberTypeName
if memberTypeName notin known:
let ty = nimTypeOf(scm, known, bn.altBranch, memberTypeName)
known[memberTypeName] = typeDef(bn.altBranch, memberTypeName, ty)
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:
@ -169,44 +243,36 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
of 2:
if not isConst(scm, bn.altBranch.nodes[1]):
let label = bn.ident
recList.add nkIdentDefs.newNode.add(
label.toExport,
nimTypeOf(scm, known, bn.altBranch.nodes[1], $label),
newEmpty())
let branch = typeIdent(scm, bn.altBranch.nodes[1])
orEmbed result, branch
recList.add identDef(bn, label.toExport, branch)
else:
recList.add nkIdentDefs.newNode.add(
bn.ident.toExport,
memberTypeIdent,
newEmpty())
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
recList.add(nkIdentDefs.newNode.add(
label.toExport,
nimTypeOf(scm, known, bn.altBranch, $label),
newEmpty()))
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 nkIdentDefs.newNode.add(
bn.ident.toExport,
memberTypeIdent,
newEmpty())
recList.add identDef(bn, bn.ident.toExport, memberType)
recCase.add nkOfBranch.newNode.add(disc, recList)
result = nn(nkRefTy, nn(nkObjectTy,
result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(),
newEmpty(),
nn(nkRecList, recCase)))
of snkAny:
result = ident"Preserve"
result = (ident"Preserve", true)
of snkAtom:
result = typeIdent(sn)
result = typeIdent(scm, sn)
of snkEmbedded:
result = nimTypeOf(scm, known, sn.embed)
of snkLiteral:
result = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim
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"
@ -216,30 +282,37 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
of pkByteString: nn(
nkBracketExpr, ident"seq", ident"byte")
of pkSymbol: ident"string"
of pkRecord: ident"Preserve"
of pkRecord: preserveIdent()
of pkSequence: nn(
nkBracketExpr, ident"seq", ident"Preserve")
nkBracketExpr, ident"seq", preserveIdent())
of pkSet: nn(
nkBracketExpr, ident"HashSet", ident"Preserve")
nkBracketExpr, ident"HashSet", preserveIdent())
of pkDictionary: nn(
nkBracketExpr, ident"Table", ident"Preserve", ident"Preserve")
nkBracketExpr, ident"TableRef", preserveIdent(), preserveIdent())
of pkEmbedded:
raiseAssert "this should never happen"
of snkSequenceOf:
result = nkBracketExpr.newNode.add(
result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"seq",
nimTypeOf(scm, known, sn.child))
parameterize(result))
of snkSetOf:
result = nkBracketExpr.newNode.add(
result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"HashedSet",
nimTypeOf(scm, known, sn.child))
parameterize(result))
of snkDictOf:
result = nkBracketExpr.newNode.add(
ident"Table",
nimTypeOf(scm, known, sn.nodes[0]),
nimTypeOf(scm, known, sn.nodes[1]))
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 = nn(nkObjectTy,
result.node = nn(nkObjectTy,
newEmpty(),
newEmpty(),
nn(nkRecList, nn(nkDiscardStmt, newEmpty())))
@ -247,62 +320,55 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
let recList = nkRecList.newNode()
for i, field in sn.nodes:
if i > 0:
let id = field.ident
recList.add nkIdentDefs.newNode.add(
id.toExport,
nimTypeOf(scm, known, field, $id),
newEmpty())
result = nn(nkRefTy, nn(nkObjectTy,
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:
# TODO: the variable part
result = nkTupleTy.newNode
result.node = nkTupleTy.newNode
for tn in sn.nodes:
if not isConst(scm, sn):
result.add nkIdentDefs.newNode.add(
tn.ident, nimTypeOf(scm, known, tn), newEmpty())
let fieldType = nimTypeOf(scm, known, tn)
orEmbed result, fieldType
result.node.add identDef(sn, tn.ident, fieldType)
of snkVariableTuple:
result = nkTupleTy.newNode
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.add nkIdentDefs.newNode.add(
result.node.add identDef(
tn,
tn.ident,
nn(nkBracketExpr, ident"seq", nimTypeOf(scm, known, tn)),
newEmpty())
nn(nkBracketExpr, ident"seq", fieldType.node),
fieldType.embeddable)
else:
result.add nkIdentDefs.newNode.add(
tn.ident, nimTypeOf(scm, known, tn), newEmpty())
result.node.add identDef(tn, tn.ident, fieldType)
of snkDictionary:
result = nkTupleTy.newNode
result.node = nkTupleTy.newNode
for i in countup(0, sn.nodes.high, 2):
let id = ident(sn.nodes[i+0])
result.add nkIdentDefs.newNode.add(
id,
nimTypeOf(scm, known, sn.nodes[i+1], $id),
newEmpty())
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:
if sn.refPath.len == 1:
let
refName = sn.refPath[0]
refDef = scm.definitions[refName]
case refDef.kind
of snkDictOf:
result = nimTypeOf(scm, known, refDef, refName)
else: result = typeIdent(sn)
else:
result = typeIdent(sn)
result = typeIdent(scm, sn)
else:
result = nkCommentStmt.newNode
result.comment = result.comment & "Missing type generator for " & $sn.kind & " " & $sn
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]; name: string; def: SchemaNode) =
proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: SchemaNode) =
case def.kind
of snkLiteral:
var stmts = nn(nkStmtList)
@ -311,7 +377,7 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
discard stmts.add newIntNode(nkIntLit, def.value.int)
of pkSymbol:
discard stmts.add nn(nkCall,
ident"symbol",
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")
@ -320,8 +386,8 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
let constProc= nn(nkProcDef,
exportIdent(procId),
newEmpty(),
newEmpty(),
nn(nkFormalParams, ident"Preserve"),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
nn(nkFormalParams, preserveIdent()),
newEmpty(),
newEmpty(),
stmts)
@ -329,18 +395,18 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
result.add constProc
else: discard
proc toNimLit(sn: SchemaNode): PNode =
proc nimLit(scm: Schema; sn: SchemaNode): PNode =
assert(sn.kind == snkLiteral, $sn)
case sn.value.kind
of pkSymbol:
nkCall.newNode.add(
ident"symbol",
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, ident"Preserve")
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)
@ -370,8 +436,7 @@ proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
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"toPreserve", nn(nkDotExpr, ident, field.ident), ident"E")
let seqConstr = nn(nkPrefix, ident"@", seqBracket)
let colonExpr = nn(nkExprColonExpr, ident"sequence")
if sn.kind == snkTuple:
@ -383,10 +448,11 @@ proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
nn(nkDotExpr,
nn(nkCall, ident"toPreserve",
nn(nkDotExpr,
ident, sn.nodes[sn.nodes.high].ident)),
ident, sn.nodes[sn.nodes.high].ident),
ident"E"),
ident"sequence"))
nn(nkObjConstr,
ident"Preserve",
preserveIdent(),
nn(nkExprColonExpr, ident"kind", ident"pkSequence"),
colonExpr)
@ -397,7 +463,7 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
enumId = name.ident
paramId = ident"v"
orStmts = nn(nkStmtList)
if sn.isSymbolEnum:
if isSymbolEnum(scm, sn):
let caseStmt = nn(nkCaseStmt, paramId)
for bn in sn.nodes:
caseStmt.add nn(nkOfBranch,
@ -405,7 +471,8 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
enumId,
bn.altLabel.nimIdentNormalize.ident.accQuote),
nn(nkCall,
ident"symbol", PNode(kind: nkStrLit, strVal: $bn.altLabel)))
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"))
@ -420,13 +487,13 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
else:
stmts.add nn(nkCall,
ident"toPreserve",
nn(nkDotExpr, paramId, fieldId))
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))
nn(nkDotExpr, paramId, fieldId), ident"E")
else:
raiseAssert("no case statement for " & $sn.kind & " " & $sn)
for bn in sn.nodes:
@ -443,41 +510,43 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
newEmpty(),
newEmpty(),
nn(nkFormalParams,
ident"Preserve",
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty())),
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
orStmts)
of snkRecord:
var
params = nn(nkFormalParams, ident"Preserve")
params = nn(nkFormalParams, preserveIdent())
initRecordCall = nn(nkCall,
ident"initRecord",
sn.nodes[0].toNimLit)
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 = field.typeIdent
if fieldType.kind != nkIdent or fieldType.ident.s != "Preserve":
var (fieldType, embeddable) = typeIdent(scm, field)
if not fieldType.isPreserve:
fieldType =
nn(nkInfix,
ident"|",
fieldType,
ident"Preserve")
preserveIdent())
params.add nn(nkIdentDefs,
id, fieldType, newEmpty())
initRecordCall.add(
nn(nkCall, ident"toPreserve", id))
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,
exportIdent(procId),
newEmpty(),
procId.ident.accQuote.toExport,
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
params,
newEmpty(),
newEmpty(),
@ -486,21 +555,24 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
block:
let paramId = name.toLowerAscii.ident.accQuote
initRecordCall = nn(nkCall,
ident"initRecord",
sn.nodes[0].toNimLit)
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))
nn(nkDotExpr, paramId, field.ident),
ident"E")
result.add nn(nkProcDef,
exportIdent("toPreserveHook"),
newEmpty(),
newEmpty(),
nn(nkFormalParams,
ident"Preserve",
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty())),
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList, initRecordCall))
@ -511,9 +583,11 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
newEmpty(),
newEmpty(),
nn(nkFormalParams,
ident"Preserve",
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty())),
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList, tupleConstructor(scm, sn, paramId)))
@ -534,9 +608,6 @@ proc collectRefImports(imports: PNode; sn: SchemaNode) =
collectRefImports(imports, child)
proc collectRefImports(imports: PNode; scm: Schema) =
if scm.embeddedType.contains {'.'}:
let m = split(scm.embeddedType, '.', 1 )[0]
imports.add ident(m)
for _, def in scm.definitions:
collectRefImports(imports, def)
@ -548,35 +619,39 @@ proc generateNimFile*(scm: Schema; path: string) =
megaType: PNode
for name, def in scm.definitions.pairs:
if isConst(scm, def):
generateConstProcs(procs, name, 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 = ident(name)
megaType = defIdent
else:
megaType = nn(nkInfix,
ident"|", megaType, ident(name))
let t = nimTypeOf(scm, knownTypes, def, name)
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, newEmpty(), t)
knownTypes[name] = (nkTypeDef.newNode.add(
name.ident.toExport, embeddingParams(false), t),
embeddable)
else:
if def.kind == snkRecord:
knownTypes[name] = typeDef(def, name, t)
knownTypes[name] = (typeDef(def, name, t, embeddable), embeddable)
else:
case t.kind
of nkEnumTy:
knownTypes[name] = nn(nkTypeDef,
knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.toExport,
nn(nkPragma, ident"pure")),
newEmpty(),
t)
t), false)
of nkDistinctTy:
knownTypes[name] = nn(nkTypeDef,
knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.toExport,
nn(nkPragma,
@ -584,12 +659,13 @@ proc generateNimFile*(scm: Schema; path: string) =
ident"borrow",
accQuote(ident".")))),
newEmpty(),
t)
t), embeddable)
else:
knownTypes[name] = nn(nkTypeDef,
name.ident.toExport, newEmpty(), t)
knownTypes[name] = (nn(nkTypeDef,
name.ident.toExport, embeddingParams(embeddable), t),
embeddable)
generateProcs(procs, scm, name, def)
for typeDef in knownTypes.values:
for (typeDef, _) in knownTypes.values:
typeSection.add typeDef
var imports = nkImportStmt.newNode.add(
ident"std/typetraits",
@ -598,7 +674,7 @@ proc generateNimFile*(scm: Schema; path: string) =
procs.add nn(nkProcDef,
"$".ident.accQuote.toExport,
newEmpty(),
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams,
ident"string",
nn(nkIdentDefs,
@ -608,11 +684,12 @@ proc generateNimFile*(scm: Schema; path: string) =
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"$", nn(nkCall, ident"toPreserve", ident"x"))))
nn(nkCall, ident"$",
nn(nkCall, ident"toPreserve", ident"x", ident"E"))))
procs.add nn(nkProcDef,
"encode".ident.accQuote.toExport,
newEmpty(),
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams,
nn(nkBracketExpr, ident"seq", ident"byte"),
nn(nkIdentDefs,
@ -622,7 +699,8 @@ proc generateNimFile*(scm: Schema; path: string) =
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"encode", nn(nkCall, ident"toPreserve", ident"x"))))
nn(nkCall, ident"encode", nn(nkCall,
ident"toPreserve", ident"x", ident"E"))))
var module = newNode(nkStmtList).add(
imports,
typeSection

View File

@ -1,64 +0,0 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[macros, typetraits]
import ../preserves
type RecordClass* = object
## Type of a preserves record.
label*: Preserve
arity*: Natural
proc `$`*(rec: RecordClass): string =
$rec.label & "/" & $rec.arity
proc isClassOf*(rec: RecordClass; val: Preserve): bool =
## Compare the label and arity of ``val`` to the record type ``rec``.
if val.kind == pkRecord:
assert(val.record.len > 0)
result = val.label == rec.label and rec.arity == val.arity
proc classOf*(val: Preserve): RecordClass =
## Derive the ``RecordClass`` of ``val``.
if val.kind != pkRecord:
raise newException(Defect, "cannot derive class of non-record value " & $val)
assert(val.record.len > 0)
RecordClass(label: val.label, arity: val.arity)
proc classOf*[T](x: T): RecordClass =
## Derive the ``RecordClass`` of ``x``.
when not T.hasCustomPragma(record): {.error: "no {.record.} pragma on " & $T.}
result.label = preserves.symbol(T.getCustomPragmaVal(record))
for k, v in x.fieldPairs: inc(result.arity)
proc classOf*(T: typedesc[tuple]): RecordClass =
## Derive the ``RecordClass`` of ``T``.
when not T.hasCustomPragma(record): {.error: "no {.record.} pragma on " & $T.}
RecordClass(
label: preserves.symbol(T.getCustomPragmaVal(record)),
arity: tupleLen(T))
proc init*(rec: RecordClass; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
assert(fields.len == rec.arity, $(fields.toPreserve) & " (arity " & $fields.len & ") is not of arity " & $rec.arity)
result = initRecord(rec.label, fields)
proc init*(T: typedesc[tuple]; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
init(classOf(T), fields)
proc `%`*(rec: RecordClass; fields: openArray[Preserve]): Preserve =
## Initialize a simple record value.
init(rec, fields)
proc `%`*(rec: RecordClass; field: Preserve): Preserve =
## Initialize a simple record value.
init(rec, [field])
proc `%`*[T](rec: RecordClass; field: T): Preserve =
## Initialize a simple record value.
init(rec, [toPreserve field])
proc `%`*(T: typedesc[tuple]; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
`%`(classOf(T), fields)

View File

@ -11,6 +11,8 @@ import npeg
import ../preserves, ./parse, ./pegs
type
Value = Preserve[void]
Stack = seq[tuple[node: SchemaNode, pos: int]]
SchemaNodeKind* = enum
@ -52,7 +54,7 @@ type
of snkEmbedded:
embed*: SchemaNode
of snkLiteral:
value*: Preserve
value*: Value
of snkNamed:
name*: string
pattern*: SchemaNode
@ -271,7 +273,7 @@ const parser = peg("Schema", p: ParseState):
AltRecord <- '<' * >id * *(S * NamedPattern) * '>':
let
id = SchemaNode(kind: snkLiteral, value: symbol($1))
id = SchemaNode(kind: snkLiteral, value: symbol[void]($1))
n = SchemaNode(kind: snkAlt,
altLabel: $1,
altBranch: snkRecord.newSchemaNode.add(id).add(takeStackAt()))

View File

@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[options, unittest]
import bigints, preserves, preserves/records
import std/[options, tables, unittest]
import bigints, preserves
suite "conversions":
test "dictionary":
@ -30,7 +30,15 @@ suite "conversions":
check(prs.kind == pkRecord)
check($prs == """<foo 1 2 <bar "ku">>""")
check(preserveTo(prs, Foobar) == some(tup))
check(classOf(tup) == classOf(prs))
test "tables":
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"}""")
var c: Table[int, string]
check(fromPreserve(c, b))
check(a == c)
suite "%":
template check(p: Preserve; s: string) =