Compare commits

..

No commits in common. "2dd63903f05b963aaefcc7a12bb11b078ae7eed5" and "75c176ddb667c4985ba9e198ee76bb0c9541e48d" have entirely different histories.

7 changed files with 363 additions and 464 deletions

View File

@ -10,14 +10,16 @@ from std/macros import hasCustomPragma, getCustomPragmaVal
type
PreserveKind* = enum
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol,
pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
pkRecord, pkSequence, pkSet, pkDictionary
const
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol}
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
type
Preserve*[E = void] {.acyclic.} = object
DictEntry = tuple[key: Preserve, val: Preserve]
Preserve* {.acyclic.} = object
case kind*: PreserveKind
of pkBoolean:
bool*: bool
@ -36,24 +38,18 @@ type
of pkSymbol:
symbol*: string
of pkRecord:
record*: seq[Preserve[E]] # label is last
record*: seq[Preserve] # label is last
of pkSequence:
sequence*: seq[Preserve[E]]
sequence*: seq[Preserve]
of pkSet:
set*: seq[Preserve[E]]
set*: seq[Preserve]
# TODO: HashSet
of pkDictionary:
dict*: seq[DictEntry[E]]
dict*: seq[DictEntry]
# TODO: Tables
of pkEmbedded:
embed*: E
embedded*: bool
## Flag to mark embedded Preserves
DictEntry[E] = tuple[key: Preserve[E], val: Preserve[E]]
proc `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
proc `==`*(x, y: Preserve): bool =
## Check `x` and `y` for equivalence.
if x.kind == y.kind and x.embedded == y.embedded:
case x.kind
@ -83,12 +79,6 @@ proc `==`*[A, B](x: Preserve[A]; y: Preserve[B]): 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):
@ -96,7 +86,7 @@ proc `<`(x, y: string | seq[byte]): bool =
if x[i] != y[i]: return false
x.len < y.len
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
proc `<`*(x, y: Preserve): bool =
## Preserves have a total order over Values. Check if `x` is ordered before `y`.
if x.embedded != y.embedded:
result = y.embedded
@ -148,9 +138,6 @@ proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): 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`.
@ -185,8 +172,6 @@ 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 =
@ -239,37 +224,39 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
return
pr.dict.add((key, val, ))
proc symbol*[E](s: string): Preserve[E] {.inline.} =
proc symbol*(s: string; E = void): Preserve {.inline.} =
## Create a Preserves symbol value.
Preserve[E](kind: pkSymbol, symbol: s)
Preserve(kind: pkSymbol, symbol: s)
proc initRecord*[E](label: Preserve[E]; arity: Natural = 0): Preserve[E] =
proc initRecord*(label: Preserve; arity = 0): Preserve =
## Create a Preserves record value.
result = Preserve[E](kind: pkRecord, record: newSeq[Preserve[E]](arity.succ))
result = Preserve(kind: pkRecord, record: newSeq[Preserve](arity.succ))
result.record[arity] = label
proc initRecord*[E](label: Preserve[E]; args: varargs[Preserve[E]]): Preserve[E] =
proc initRecord*(label: Preserve; args: varargs[Preserve]): Preserve =
## Create a Preserves record value.
result = Preserve[E](kind: pkRecord,
record: newSeqOfCap[Preserve[E]](1+args.len))
result = Preserve(kind: pkRecord,
record: newSeqOfCap[Preserve](1+args.len))
for arg in args:
result.record.add(arg)
result.record.add(label)
proc initSequence*[E](len: Natural = 0): Preserve[E] =
## Create a Preserves sequence value.
Preserve[E](kind: pkSequence, sequence: newSeq[Preserve[E]](len))
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 initSet*[E](): Preserve[E] = Preserve[E](kind: pkSet)
proc initSequence*(len = 0): Preserve =
## Create a Preserves sequence value.
Preserve(kind: pkSequence, record: newSeq[Preserve](len))
proc initSet*(): Preserve = Preserve(kind: pkSet)
## Create a Preserves set value.
proc initDictionary*[E](): Preserve[E] = Preserve[E](kind: pkDictionary)
proc initDictionary*(): Preserve = Preserve(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
@ -340,10 +327,8 @@ 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*[E](pr: Preserve[E]): bool {.inline.} =
func isEmbedded*(pr: Preserve): bool {.inline.} = pr.embedded
## 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.
@ -381,7 +366,7 @@ proc readVarint(s: Stream): int =
break
shift.inc 7
proc write*[E](str: Stream; pr: Preserve[E]) =
proc write*(str: Stream; pr: Preserve) =
## Write the binary-encoding of a Preserves value to a stream.
if pr.embedded: str.write(0x86'u8)
case pr.kind:
@ -475,19 +460,15 @@ proc write*[E](str: Stream; pr: Preserve[E]) =
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*[E](pr: Preserve[E]): seq[byte] =
proc encode*(pr: Preserve): 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; E = void): Preserve[E] =
proc decodePreserves*(s: Stream): Preserve =
## Decode a Preserves value from a binary-encoded stream.
proc assertStream(check: bool) =
if not check:
@ -495,61 +476,61 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
const endMarker = 0x84
let tag = s.readUint8()
case tag
of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
of 0x80: result = Preserve(kind: pkBoolean, bool: false)
of 0x81: result = Preserve(kind: pkBoolean, bool: true)
of 0x82:
when system.cpuEndian == bigEndian:
result = Preserve[E](kind: pkFloat, float: s.readFloat32())
result = Preserve(kind: pkFloat, float: s.readFloat32())
else:
result = Preserve[E](kind: pkFloat)
result = Preserve(kind: pkFloat)
var be = s.readFloat32()
swapEndian32(result.float.addr, be.addr)
of 0x83:
when system.cpuEndian == bigEndian:
result = Preserve[E](kind: pkDouble, double: s.readFloat64())
result = Preserve(kind: pkDouble, double: s.readFloat64())
else:
result = Preserve[E](kind: pkDouble)
result = Preserve(kind: pkDouble)
var be = s.readFloat64()
swapEndian64(result.double.addr, be.addr)
of 0x86:
result = decodePreserves(s, E)
result = decodePreserves(s)
result.embedded = true
of 0xb1:
result = Preserve[E](kind: pkString)
result = Preserve(kind: pkString)
let len = s.readVarint()
result.string = s.readStr(len)
of 0xb2:
result = Preserve[E](kind: pkByteString)
result = Preserve(kind: pkByteString)
let len = s.readVarint()
result.bytes = cast[seq[byte]](s.readStr(len))
of 0xb3:
let len = s.readVarint()
result = Preserve[E](kind: pkSymbol, symbol: s.readStr(len))
result = Preserve(kind: pkSymbol, symbol: s.readStr(len))
of 0xb4:
result = Preserve[E](kind: pkRecord)
var label = decodePreserves(s, E)
result = Preserve(kind: pkRecord)
var label = decodePreserves(s)
while s.peekUint8() != endMarker:
result.record.add decodePreserves(s, E)
result.record.add decodePreserves(s)
result.record.add(move label)
discard s.readUint8()
of 0xb5:
result = Preserve[E](kind: pkSequence)
result = Preserve(kind: pkSequence)
while s.peekUint8() != endMarker:
result.sequence.add decodePreserves(s, E)
result.sequence.add decodePreserves(s)
discard s.readUint8()
of 0xb6:
result = Preserve[E](kind: pkSet)
result = Preserve(kind: pkSet)
while s.peekUint8() != endMarker:
incl(result, decodePreserves(s, E))
incl(result, decodePreserves(s))
discard s.readUint8()
of 0xb7:
result = Preserve[E](kind: pkDictionary)
result = Preserve(kind: pkDictionary)
while s.peekUint8() != endMarker:
result[decodePreserves(s, E)] = decodePreserves(s, E)
result[decodePreserves(s)] = decodePreserves(s)
discard s.readUint8()
of 0xb0:
let len = s.readVarint()
result = Preserve[E](kind: pkBigInteger, bigint: initBigint 0)
result = Preserve(kind: pkBigInteger, bigint: initBigint 0)
for _ in 1..len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32
of endMarker:
@ -558,29 +539,29 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
case 0xf0 and tag
of 0x90:
var n = tag.BiggestInt
result = Preserve[E](kind: pkSignedInteger,
result = Preserve(kind: pkSignedInteger,
int: n - (if n > 0x9c: 0xa0 else: 0x90))
of 0xa0:
let len = (tag.int and 0x0f) + 1
if len <= 8:
result = Preserve[E](kind: pkSignedInteger, int: s.readUint8().BiggestInt)
result = Preserve(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[E](kind: pkBigInteger)
result = Preserve(kind: pkBigInteger)
for i in 0..<len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32
else:
assertStream(false)
proc decodePreserves*(s: string; E = void): Preserve[E] =
proc decodePreserves*(s: string): Preserve =
## Decode a string of binary-encoded Preserves.
decodePreserves(s.newStringStream, E)
s.newStringStream.decodePreserves
proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
proc decodePreserves*(s: seq[byte]): Preserve =
## Decode a byte-string of binary-encoded Preserves.
decodePreserves(cast[string](s), E)
cast[string](s).decodePreserves
template record*(label: string) {.pragma.}
## Serialize this object or tuple as a record. See ``toPreserve``.
@ -588,77 +569,61 @@ template record*(label: string) {.pragma.}
template unpreservable*() {.pragma.}
## Pragma to forbid a type from being converted by ``toPreserve``.
proc toPreserve*[T](x: T; E = void): Preserve[E] =
proc toPreserve*[T](x: T): Preserve =
## Serializes ``x`` to Preserves. Can be customized by defining
## ``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)
## ``toPreserveHook(x: T)`` in the calling scope.
when (T is Preserve): result = x
elif compiles(toPreserveHook(x)):
result = toPreserveHook(x)
elif T is Bigint:
result = Preserve[E](kind: pkBigInteger, bigint: x)
result = Preserve(kind: pkBigInteger, bigint: x)
elif T is seq[byte]:
result = Preserve[E](kind: pkByteString, bytes: x)
result = Preserve(kind: pkByteString, bytes: x)
elif T is array | seq:
result = Preserve[E](kind: pkSequence, sequence: newSeqOfCap[Preserve[E]](x.len))
for v in x.items: result.sequence.add(toPreserve(v, E))
result = Preserve(kind: pkSequence, sequence: newSeqOfCap[Preserve](x.len))
for v in x.items: result.sequence.add(toPreserve(v))
elif T is bool:
result = Preserve[E](kind: pkBoolean, bool: x)
result = Preserve(kind: pkBoolean, bool: x)
elif T is distinct:
result = toPreserve(x.distinctBase, E)
result = toPreserve(x.distinctBase)
elif T is float:
result = Preserve[E](kind: pkFloat, float: x)
result = Preserve(kind: pkFloat, float: x)
elif T is float64:
result = Preserve[E](kind: pkDouble, double: x)
result = Preserve(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)))
result = Preserve(kind: pkRecord)
for _, f in x.fieldPairs: result.record.add(toPreserve(f))
result.record.add(symbol(T.getCustomPragmaVal(record)))
else:
result = Preserve[E](kind: pkDictionary)
result = Preserve(kind: pkDictionary)
for k, v in x.fieldPairs:
result[symbol[E](k)] = toPreserve(v, E)
result[symbol(k)] = toPreserve(v)
elif T is Ordinal:
result = Preserve[E](kind: pkSignedInteger, int: x.ord.BiggestInt)
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref:
if system.`==`(x, nil): result = symbol[E]("null")
else: result = toPreserve(x[], E)
if system.`==`(x, nil): result = symbol("null")
else: result = toPreserve(x[])
elif T is string:
result = Preserve[E](kind: pkString, string: x)
result = Preserve(kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve[E](kind: pkSignedInteger, int: x.BiggestInt)
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt)
else:
raiseAssert("unpreservable type" & $T)
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] =
proc toPreserveHook*[T](set: HashSet[T]): Preserve =
## Hook for preserving ``HashSet``.
result = Preserve[E](kind: pkSet, set: newSeqOfCap[Preserve[E]](set.len))
for e in set: result.incl toPreserve(e, E)
Preserve(kind: pkSet, set: set.map(toPreserve))
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B], E: typedesc): Preserve[E] =
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B]): Preserve =
## Hook for preserving ``Table``.
result = initDictionary[E]()
for k, v in table.pairs: result[toPreserve(k, E)] = toPreserve(v, E)
result = initDictionary()
for k, v in table.pairs: result[k.toPreserve] = v.toPreserve
proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
proc fromPreserve*[T](v: var T; pr: Preserve): 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
@ -672,8 +637,6 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): 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:
@ -732,7 +695,7 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
for key, val in v.fieldPairs:
inc fieldCount
for (pk, pv) in pr.dict.items:
var sym = symbol[E](key)
var sym = symbol(key)
if sym == pk:
result = result and fromPreserve(val, pv)
break
@ -743,7 +706,7 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
v = (T)pr.int
result = true
elif T is ref:
if pr != symbol[E]("null"):
if pr != symbol("null"):
new v
result = fromPreserve(v[], pr)
elif T is string:
@ -772,28 +735,13 @@ proc preserveTo*(pr: Preserve; T: typedesc): Option[T] =
if fromPreserve(v, pr):
result = some(move v)
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 fromPreserveHook*[A,B,E](t: var (Table[A,B]|TableRef[A,B]); pr: Preserve[E]): bool =
proc fromPreserveHook*[A,B,E](t: var Table[A,B]|TableRef[A,B]; pr: Preserve): 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
for k, v in pr.pairs:
t[preserveTo(k, A)] = preserveTo(v, B)
result = true
proc concat[E](result: var string; pr: Preserve[E]) =
proc concat(result: var string; pr: Preserve) =
if pr.embedded: result.add("#!")
case pr.kind:
of pkBoolean:
@ -856,20 +804,6 @@ proc concat[E](result: var string; pr: Preserve[E]) =
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; E: typedesc): Preserve[E] =
proc toPreserveHook*(js: JsonNode): Preserve =
case js.kind
of JString:
result = Preserve[E](kind: pkString, string: js.str)
result = Preserve(kind: pkString, string: js.str)
of JInt:
result = Preserve[E](kind: pkSignedInteger, int: js.num)
result = Preserve(kind: pkSignedInteger, int: js.num)
of JFloat:
result = Preserve[E](kind: pkDouble, double: js.fnum)
result = Preserve(kind: pkDouble, double: js.fnum)
of JBool:
result = case js.bval
of false: symbol[E]"false"
of true: symbol[E]"true"
of false: symbol"false"
of true: symbol"true"
of JNull:
result = symbol[E]"null"
result = symbol"null"
of JObject:
result = Preserve[E](kind: pkDictionary)
result = Preserve(kind: pkDictionary)
for key, val in js.fields.pairs:
result[Preserve[E](kind: pkString, string: key)] = toPreserveHook(val, E)
result[Preserve(kind: pkString, string: key)] = toPreserveHook(val)
of JArray:
result = Preserve[E](kind: pkSequence,
sequence: newSeq[Preserve[E]](js.elems.len))
result = Preserve(kind: pkSequence,
sequence: newSeq[Preserve](js.elems.len))
for i, e in js.elems:
result.sequence[i] = toPreserveHook(e, E)
result.sequence[i] = toPreserveHook(e)
proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
proc fromPreserveHook*(js: var JsonNode; prs: Preserve): bool =
case prs.kind:
of pkBoolean:
js = newJBool(prs.bool)
@ -67,16 +67,6 @@ proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
else: return false
true
proc toJsonHook*[E](pr: Preserve[E]): JsonNode =
if not fromPreserveHook(result, pr):
proc toJsonHook*(pr: Preserve): JsonNode =
if not fromPreserve(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,15 +6,14 @@ import npeg
import ../preserves, ./pegs
type
Value = Preserve[void]
Frame = tuple[value: Value, pos: int]
Frame = tuple[value: Preserve, pos: int]
Stack = seq[Frame]
proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n)
template pushStack(v: Value) = stack.add((v, capture[0].si))
template pushStack(v: Preserve) = stack.add((v, capture[0].si))
proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
proc parsePreserves*(text: string): Preserve {.gcsafe.} =
const pegParser = peg("Document", stack: Stack):
# Override rules from pegs.nim
@ -22,7 +21,7 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
Preserves.Record <- Preserves.Record:
var
record: seq[Value]
record: seq[Preserve]
labelOff: int
while stack[labelOff].pos < capture[0].si:
inc labelOff
@ -30,18 +29,18 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
record.add(move stack[i].value)
record.add(move stack[labelOff].value)
stack.shrink record.len
pushStack Value(kind: pkRecord, record: move record)
pushStack Preserve(kind: pkRecord, record: move record)
Preserves.Sequence <- Preserves.Sequence:
var sequence: seq[Value]
var sequence: seq[Preserve]
for frame in stack.mitems:
if frame.pos > capture[0].si:
sequence.add(move frame.value)
stack.shrink sequence.len
pushStack Value(kind: pkSequence, sequence: move sequence)
pushStack Preserve(kind: pkSequence, sequence: move sequence)
Preserves.Dictionary <- Preserves.Dictionary:
var prs = Value(kind: pkDictionary)
var prs = Preserve(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
@ -49,7 +48,7 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
pushStack prs
Preserves.Set <- Preserves.Set:
var prs = Value(kind: pkSet)
var prs = Preserve(kind: pkSet)
for frame in stack.mitems:
if frame.pos > capture[0].si:
prs.incl(move frame.value)
@ -58,36 +57,36 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
Preserves.Boolean <- Preserves.Boolean:
case $0
of "#f": pushStack Value(kind: pkBoolean)
of "#t": pushStack Value(kind: pkBoolean, bool: true)
of "#f": pushStack Preserve(kind: pkBoolean)
of "#t": pushStack Preserve(kind: pkBoolean, bool: true)
else: discard
Preserves.Float <- Preserves.Float:
pushStack Value(kind: pkFloat, float: parseFloat($1))
pushStack Preserve(kind: pkFloat, float: parseFloat($1))
Preserves.Double <- Preserves.Double:
pushStack Value(kind: pkDouble)
pushStack Preserve(kind: pkDouble)
let i = stack.high
discard parseBiggestFloat($0, stack[i].value.double)
Preserves.SignedInteger <- Preserves.SignedInteger:
pushStack Value(kind: pkSignedInteger, int: parseInt($0))
pushStack Preserve(kind: pkSignedInteger, int: parseInt($0))
Preserves.String <- Preserves.String:
pushStack Value(kind: pkString, string: unescape($0))
pushStack Preserve(kind: pkString, string: unescape($0))
Preserves.charByteString <- Preserves.charByteString:
let s = unescape($1)
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](s))
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s))
Preserves.hexByteString <- Preserves.hexByteString:
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1)))
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1)))
Preserves.b64ByteString <- Preserves.b64ByteString:
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
Preserves.Symbol <- Preserves.Symbol:
pushStack Value(kind: pkSymbol, symbol: $0)
pushStack Preserve(kind: pkSymbol, symbol: $0)
Preserves.Embedded <- Preserves.Embedded:
var v = stack.pop.value
@ -95,7 +94,7 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
pushStack v
Preserves.Compact <- Preserves.Compact:
pushStack decodePreserves(stack.pop.value.bytes, void)
pushStack decodePreserves(stack.pop.value.bytes)
var stack: Stack
let match = pegParser.match(text, stack)

View File

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

64
src/preserves/records.nim Normal file
View File

@ -0,0 +1,64 @@
# 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,8 +11,6 @@ import npeg
import ../preserves, ./parse, ./pegs
type
Value = Preserve[void]
Stack = seq[tuple[node: SchemaNode, pos: int]]
SchemaNodeKind* = enum
@ -54,7 +52,7 @@ type
of snkEmbedded:
embed*: SchemaNode
of snkLiteral:
value*: Value
value*: Preserve
of snkNamed:
name*: string
pattern*: SchemaNode
@ -273,7 +271,7 @@ const parser = peg("Schema", p: ParseState):
AltRecord <- '<' * >id * *(S * NamedPattern) * '>':
let
id = SchemaNode(kind: snkLiteral, value: symbol[void]($1))
id = SchemaNode(kind: snkLiteral, value: symbol($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, tables, unittest]
import bigints, preserves
import std/[options, unittest]
import bigints, preserves, preserves/records
suite "conversions":
test "dictionary":
@ -30,15 +30,7 @@ suite "conversions":
check(prs.kind == pkRecord)
check($prs == """<foo 1 2 <bar "ku">>""")
check(preserveTo(prs, Foobar) == some(tup))
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)
check(classOf(tup) == classOf(prs))
suite "%":
template check(p: Preserve; s: string) =