Compare commits

...

10 Commits

5 changed files with 254 additions and 168 deletions

View File

@ -1,6 +1,6 @@
# Package
version = "20231230"
version = "20240106"
author = "Emery Hemingway"
description = "data model and serialization format"
license = "Unlicense"

View File

@ -142,15 +142,6 @@ proc `&`*(x: Value; y: seq[Value]): Value =
raise newException(ValueError, "cannot concatenate to non-sequence value")
result = Value(kind: pkSequence, sequence: x.sequence & y)
proc getOrDefault(pr, key: Value): Value =
## Retrieves the value of `pr[key]` if `pr` is a dictionary containing `key`
## or returns the `#f` Preserves value.
if pr.kind == pkDictionary:
for (k, v) in pr.dict:
if key == k:
result = v
break
proc pop*(pr: var Value; key: Value; val: var Value): bool =
## Deletes the `key` from a Preserves dictionary.
## Returns true, if the key existed, and sets `val` to the mapping
@ -207,6 +198,11 @@ proc initRecord*(label: string; args: varargs[Value]): Value {.inline.} =
## Create a Preserves record value.
initRecord(toSymbol(label), args)
proc toRecord*(items: varargs[Value, toPreserves]): Value =
assert items.len > 0
result = initRecord(items[0], items.len.pred)
for i in 0..<items.high: result.record[i] = items[succ i]
proc initSequence*(len: Natural = 0): Value =
## Create a Preserves sequence value.
Value(kind: pkSequence, sequence: newSeq[Value](len))
@ -435,9 +431,15 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
elif T.hasCustomPragma(preservesDictionary):
result = initDictionary()
for key, val in x.fieldPairs:
var pr = fieldToPreserve(key, val)
applyEmbed(key, pr)
result[toSymbol(key)] = pr
when val is Option:
if val.isSome:
var pr = fieldToPreserve(key, val.get)
applyEmbed(key, pr)
result[key.toSymbol] = pr
else:
var pr = fieldToPreserve(key, val)
applyEmbed(key, pr)
result[key.toSymbol] = pr
sortDict(result)
else:
{.warning: "failed to preserve object " & $T .}
@ -486,6 +488,9 @@ proc toPreservesHook*[A, B](table: Table[A, B]|TableRef[A, B]): Value =
result[toPreserves(k)] = toPreserves(v)
sortDict(result)
proc toPreservesHook*(o: Option): Value =
o.get.toPreserves
proc fromAtom*[T](v: var T; a: ATom): bool =
if T is Atom:
v = a
@ -710,12 +715,17 @@ proc fromPreserves*[T](v: var T; pr: Value): bool {.gcsafe.} =
if pr.isDictionary:
result = true
var i: int
for key, _ in fieldPairs(v):
let val = pr.getOrDefault(toSymbol(key))
result = result and fieldFromPreserve(
key, v.dot(key), val)
for key, field in fieldPairs(v):
if not result: break
inc i
let val = step(pr, key.toSymbol)
when field is Option:
if val.isSome:
discard fieldFromPreserve(key, v.dot(key), val.get)
else:
inc i
result = result and val.isSome
if result:
result = result and fieldFromPreserve(key, v.dot(key), val.get)
result = result and (i <= pr.len)
elif T.hasCustomPragma(preservesOr):
for kind in typeof(T.orKind):
@ -735,9 +745,11 @@ proc fromPreserves*[T](v: var T; pr: Value): bool {.gcsafe.} =
result = true
var i: int
for key, _ in fieldPairs(v):
let val = pr.getOrDefault(toSymbol(key))
result = result and fieldFromPreserve(key, v.dot(key), val)
if not result: break
let val = step(pr, key.toSymbol)
result = result and val.isSome
if result:
result = result and fieldFromPreserve(key, v.dot(key), val.get)
inc i
result = result and (i <= pr.len)
else:
@ -806,19 +818,18 @@ proc fromPreservesHook*[A,B](t: var (Table[A,B]|TableRef[A,B]); pr: Value): bool
break
t[move a] = move b
proc fromPreservesHook*[T](opt: var Option[T]; pr: Value): bool =
opt = some(default T)
result = opt.get.fromPreserves(pr)
if not result:
opt = none(T)
when isMainModule:
var t: Table[int, string]
var pr = t.toPreservesHook()
assert fromPreservesHook(t, pr)
func step*(pr, idx: Value): Option[Value] =
## Step into `pr` by index `idx`.
## Works for sequences, records, and dictionaries.
runnableExamples:
import std/options
assert step(parsePreserves("""<foo 1 2>"""), 1.toPreserve) == some(2.toPreserve)
assert step(parsePreserves("""{ foo: 1 bar: 2}"""), "foo".toSymbol) == some(1.toPreserve)
assert step(parsePreserves("""[ ]"""), 1.toPreserve) == none(Value)
func step(pr, idx: Value): Option[Value] =
if pr.isDictionary:
for (k, v) in pr.dict.items:
if k == idx:
@ -831,8 +842,14 @@ func step*(pr, idx: Value): Option[Value] =
if i < pr.len:
result = some(pr[i])
func step*(pr: Value; path: varargs[Value]): Option[Value] =
## Step into `pr` by indexes at `path`.
func step*(pr: Value; path: varargs[Value, toPreserves]): Option[Value] =
## Step into `pr` by index `idx`.
## Works for sequences, records, and dictionaries.
runnableExamples:
import std/options
assert step(parsePreserves("""<foo 1 2>"""), 1.toPreserve) == some(2.toPreserve)
assert step(parsePreserves("""{ foo: 1 bar: 2}"""), "foo".toSymbol) == some(1.toPreserve)
assert step(parsePreserves("""[ ]"""), 1.toPreserve) == none(Value)
result = some(pr)
for index in path:
if result.isSome:
@ -865,105 +882,40 @@ proc apply*(result: var Value; op: proc(_: var Value) {.gcsafe.}) {.gcsafe.} =
recurse(e.val)
cannonicalize(result)
proc mapEmbeds*[A, B](pr: sink Value; op: proc (v: A): B): Value =
## Convert `Value` to `Value` using an `A → B` procedure.
runnableExamples:
import std/tables
type MacGuffin = ref object
stuff: void
var registry = {20: new MacGuffin}.toTable
let
a = [ 20.embed ].toPreserves(int)
b = mapEmbeds(a) do (i: int) -> MacGuffin:
registry[i]
assert typeof(b[0].unembed) is MacGuffin
if pr.embedded:
var
e: A
pr = pr # TODO: avoid copy
pr.embedded = false
if not fromPreserves(e, pr):
raise newException(ValueError, "failed to map across embedded types")
result = embed op(e)
else:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry[A]) -> DictEntry[B]:
(mapEmbeds(e.key, op), mapEmbeds(e.val, op))
of pkEmbedded:
result = embed op(pr.embed)
cannonicalize(result)
proc contract*[T](pr: sink Value; op: proc (v: T): Value {.gcsafe.}): Value {.gcsafe.} =
## Convert `Value` to `Value` using an `T → Value` procedure.
if not pr.embedded:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
contract(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
contract(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
contract(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(contract(e.key, op), contract(e.val, op))
of pkEmbedded:
proc mapEmbeds*[T](pr: sink Value; op: proc (x: T): Value {.gcsafe.}): Value {.gcsafe.} =
## Process all embeds in a `Value` that are of type `T`.
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(mapEmbeds(e.key, op), mapEmbeds(e.val, op))
of pkEmbedded:
when T is Value: result = pr
else:
if pr.embeddedRef of T:
result = embed op(T pr.embeddedRef)
cannonicalize(result)
proc expand*(pr: sink Value; op: proc (v: Value): Value {.gcsafe.}): Value {.gcsafe.} =
## Convert `Value` to `Value` using an `Value → Value` procedure.
if pr.embedded:
result = op(pr)
else:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol, pkEmbedded:
result = pr
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
expand(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
expand(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
expand(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(expand(e.key, op), expand(e.val, op))
result = op(T pr.embeddedRef)
result.embedded = true
else: result = pr
when T is Value:
if result.embedded:
result = op(pr)
cannonicalize(result)
# TODO: is this necessary?
proc getOrDefault*[T, V](pr: Value; key: string; default: V): V =
## Retrieves the value of `pr[key]` if `pr` is a dictionary containing `key`

View File

@ -6,10 +6,7 @@
# the schema module must be regenerated!
# nim c --path:../../../nim --path:.. -r ./preserves_schema_nim ../../../preserves/schema/schema.bin
# This module formally parameterized types wherever embedded values were found.
# This is no longer the case but some of that code is lingering around.
import std/[hashes, strutils, sets, tables]
import std/[hashes, sets, strutils, tables]
# Cannot use std/macros, must use compiler modules because
# we are generating code at run-time.
@ -97,7 +94,7 @@ proc isEmbedded(ts: TypeSpec): bool =
func isAtomic(r: Ref): bool =
case r.name.string
of "bool", "float", "double", "int", "string", "bytes", "symbol": true
of "bool", "float", "double", "int", "string", "bytes", "symbol": true
else: false
proc addAttrs(x: var TypeSpec; y: TypeSpec) =
@ -131,6 +128,24 @@ proc hasEmbeddedType(scm: Schema): bool =
of EmbeddedtypenameKind.false: false
of EmbeddedtypenameKind.Ref: true
proc embeddedIdentString(scm: Schema): string =
doAssert $scm.field0.embeddedType.ref.name != ""
$scm.field0.embeddedType.ref.name
proc embeddedIdent(scm: Schema): PNode =
ident(embeddedIdentString(scm))
proc parameterize(loc: Location; node: PNode; embeddable: bool): PNode =
if embeddable and
loc.schema.hasEmbeddedType and
node.kind notin {nkBracketExpr} and
(node.kind != nkIdent or node.ident.s != loc.schema.embeddedIdentString):
nkBracketExpr.newTree(node, loc.schema.embeddedIdent)
else: node
proc parameterize(loc: Location; spec: TypeSpec): PNode =
parameterize(loc, spec.node, spec.isEmbedded)
proc hash(r: Ref): Hash = r.toPreserves.hash
type RefSet = HashSet[Ref]
@ -143,8 +158,7 @@ proc attrs(loc: Location; n: NamedAlternative|NamedPattern; seen: RefSet): Attri
proc attrs(loc: Location; sp: SimplePattern; seen: RefSet): Attributes =
case sp.orKind
of SimplepatternKind.atom, SimplepatternKind.lit, SimplepatternKind.any: {}
of SimplepatternKind.embedded:
attrs(loc, sp.embedded.interface, seen)
of SimplepatternKind.embedded: {embedded}
of SimplepatternKind.seqof:
attrs(loc, sp.seqof.pattern, seen)
of SimplepatternKind.setof:
@ -302,7 +316,8 @@ proc isLiteral(loc: Location; sp: SimplePattern): bool =
of SimplepatternKind.lit:
result = true
of SimplepatternKind.embedded:
result = isLiteral(loc, sp.embedded.interface)
if not loc.schema.hasEmbeddedType:
result = isLiteral(loc, sp.embedded.interface)
else: discard
proc isLiteral(loc: Location; np: NamedPattern): bool =
@ -326,10 +341,6 @@ proc isLiteral(loc: Location; def: Definition): bool =
proc isRef(sp: SimplePattern): bool =
sp.orKind == SimplePatternKind.Ref
proc isRef(pat: Pattern): bool =
pat.orKind == PatternKind.SimplePattern and
pat.simplePattern.isRef
proc isSimple(pat: Pattern): bool =
pat.orKind == PatternKind.SimplePattern
@ -377,16 +388,22 @@ proc isDictionary(loc: Location; pat: Pattern): bool =
result = true
else: discard
proc isDictionary(loc: Location; defAnd: DefinitionAnd): bool =
result = isDictionary(loc, defAnd.field0.pattern0.pattern)
proc isDictionary(loc: Location; def: Definition): bool =
case def.orKind
of DefinitionKind.Pattern:
result = isDictionary(loc, def.pattern)
of DefinitionKind.or:
result =
isDictionary(loc, def.or.field0.pattern0.pattern) and
isDictionary(loc, def.or.field0.pattern1.pattern)
for np in def.or.field0.patternN:
if result: result = isDictionary(loc, np.pattern)
of DefinitionKind.and:
result = isDictionary(loc, def.and)
else: discard
result =
isDictionary(loc, def.and.field0.pattern0.pattern) and
isDictionary(loc, def.and.field0.pattern1.pattern)
for np in def.and.field0.patternN:
if result: result = isDictionary(loc, np.pattern)
proc isAny(loc: Location; def: Definition): bool =
case def.orKind
@ -439,8 +456,12 @@ proc typeIdent(loc: Location; sp: SimplePattern): TypeSpec =
result.attrs = key.attrs + val.attrs
of SimplepatternKind.Ref:
result = TypeSpec(node: ident(sp.ref), attrs: attrs(loc, sp))
result.node = parameterize(loc, result)
of SimplepatternKind.embedded:
result = typeIdent(loc, sp.embedded.interface)
if loc.schema.hasEmbeddedType:
result = TypeSpec(node: loc.schema.embeddedIdent())
else:
result = TypeSpec(node: ident"Value")
incl(result.attrs, embedded)
of SimplepatternKind.any, SimplepatternKind.lit:
result = TypeSpec(node: ident"Value")
@ -469,6 +490,7 @@ proc toStrLit(loc: Location; sp: SimplePattern): PNode =
var (loc, def) = deref(loc, sp.ref)
result = toStrLit(loc, def)
of SimplePatternKind.embedded:
doAssert not loc.schema.hasEmbeddedType
result = PNode(kind: nkStrLit, strVal: "#!" & toStrLit(loc, sp.embedded.interface).strVal)
else: raiseAssert $sp
@ -487,8 +509,21 @@ proc toFieldIdent(loc: Location, label: string; pat: Pattern): PNode =
proc newEmpty(): PNode = newNode(nkEmpty)
proc embeddingParams(loc: Location; embeddable: bool): PNode =
if embeddable and loc.schema.hasEmbeddedType:
nkGenericParams.newTree(
nkIdentDefs.newTree(embeddedIdent(loc.schema),
newEmpty(),
newEmpty()))
else:
newEmpty()
proc identDef(scm: Schema; a, b: PNode; embeddable: bool): PNode =
nkIdentDefs.newTree(a, b, newEmpty())
if embeddable and scm.hasEmbeddedType and b.kind notin {nkBracketExpr, nkTupleTy} and
(b.kind != nkIdent or b.ident.s != scm.embeddedIdentString):
nkIdentDefs.newTree(a, nkBracketExpr.newTree(b, embeddedIdent(scm)), newEmpty())
else:
nkIdentDefs.newTree(a, b, newEmpty())
proc identDef(scm: Schema; l: PNode; ts: TypeSpec): PNode =
identDef(scm, l, ts.node, ts.isEmbedded)
@ -537,7 +572,7 @@ proc typeDef(loc: Location; name: string; pat: SimplePattern; ty: PNode): PNode
proc typeDef(loc: Location; name: string; pat: Pattern; ty: PNode): PNode =
let
embedParams = newEmpty()
embedParams = embeddingParams(loc, isEmbedded(loc, pat))
id = name.ident.toExport
case pat.orKind
of PatternKind.CompoundPattern:
@ -579,15 +614,17 @@ proc typeDef(loc: Location; name: string; def: Definition; ty: PNode): PNode =
nkPragmaExpr.newTree(
name.ident.accQuote.toExport,
pragma),
newEmpty(),
embeddingParams(loc, isEmbedded(loc, def)),
ty)
of DefinitionKind.and:
let pragma = newEmpty()
var pragma = nkPragma.newNode
if isDictionary(loc, def):
pragma.add(ident"preservesDictionary")
nkTypeDef.newTree(
nkPragmaExpr.newTree(
name.ident.accQuote.toExport,
pragma),
newEmpty(),
embeddingParams(loc, isEmbedded(loc, def)),
ty)
of DefinitionKind.Pattern:
typeDef(loc, name, def.pattern, ty)
@ -611,7 +648,7 @@ proc addField(recList: PNode; loc: Location; known: var TypeTable; sp: SimplePat
ident"preservesLiteral",
toStrLit(loc, sp))))
recList.add identDef(scm, id, TypeSpec(node: ident"tuple[]"))
elif sp.orKind == SimplePatternKind.embedded and not scm.hasEmbeddedType:
elif sp.orKind == SimplePatternKind.embedded:
let id = nkPragmaExpr.newTree(
id, nkPragma.newTree(ident"preservesEmbedded"))
recList.add identDef(scm, id, nimTypeOf(loc, known, "", sp))
@ -623,7 +660,7 @@ proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: s
label = label(np, parentName, index)
id = label.toFieldIdent
pattern = np.pattern
if pattern.isRef or pattern.isSimple:
if pattern.isSimple:
addField(recList, loc, known, pattern.simplePattern, label)
else:
var
@ -633,20 +670,25 @@ proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: s
known[typePath] = typeDef(loc, typeName, pattern, fieldSpec.node)
recList.add identDef(loc.schema, id, ident(typeName), isEmbedded(loc, pattern))
proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: string; nsp: NamedSimplePattern; index = 0) =
proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: string; nsp: NamedSimplePattern; index: int; optional: bool) =
let
label = label(nsp, parentName, index)
id = label.toFieldIdent
pattern = nsp.pattern
if pattern.isRef:
addField(recList, loc, known, pattern, label)
var node = typeIdent(loc, pattern).node
if optional:
node = nkBracketExpr.newTree(ident"Option", node)
recList.add identDef(loc.schema, id, node, false)
else:
var
typeName = parentName & capitalizeAscii(label)
typePath = loc.schemaPath & @[Symbol typeName]
fieldSpec = nimTypeOf(loc, known, label, pattern)
if optional:
fieldSpec.node = nkBracketExpr.newTree(ident"Option", fieldSpec.node)
known[typePath] = typeDef(loc, typeName, pattern, fieldSpec.node)
recList.add identDef(loc.schema, id, ident(typeName), false)
recList.add identDef(loc.schema, id, fieldSpec.node, false)
proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName: string; cp: CompoundPattern): PNode {.discardable.} =
let scm = loc.schema
@ -655,7 +697,7 @@ proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName:
label = label(np, parentName, index)
id = label.toFieldIdent
pattern = np.pattern
if pattern.isRef or pattern.isSimple:
if pattern.isSimple:
addField(recList, loc, known, pattern.simplePattern, label)
else:
var
@ -678,11 +720,11 @@ proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName:
nkPragmaExpr.newTree(
ident(cp.tuplePrefix.variable, parentName).accQuote.toExport,
nkPragma.newTree(ident"preservesTupleTail")),
variableType.node,
parameterize(loc, variableType),
variableType.isEmbedded)
of CompoundPatternKind.dict:
for nameVal, nsp in cp.dict.entries:
recList.addField(loc, known, $nameVal, nsp)
recList.addField(loc, known, $nameVal, nsp, 0, false)
reclist
proc addFields(recList: PNode; loc: Location; known: var TypeTable; name: string; pat: SimplePattern): PNode {.discardable.} =
@ -777,8 +819,8 @@ proc nimTypeOf(loc: Location; known: var TypeTable; name: string; orDef: Definit
var memberType: TypeSpec
if isLiteral(loc, na.pattern):
memberType.node = ident"bool"
elif na.pattern.isRef:
memberType = typeIdent(loc, na.pattern)
elif na.pattern.isSimple:
memberType = typeIdent(loc, na.pattern)
else:
let
memberTypeName = name & na.variantLabel.capitalizeAscii
@ -790,6 +832,8 @@ proc nimTypeOf(loc: Location; known: var TypeTable; name: string; orDef: Definit
known[memberPath] =
typeDef(loc, memberTypeName, na.pattern, ty.node)
addAttrs(result, memberType)
memberType.node = parameterize(
loc, memberType.node, isEmbedded(loc, na.pattern))
branchRecList.add nkIdentDefs.newTree(
toFieldIdent(loc, na.variantLabel.normalize, na.pattern),
memberType.node, newEmpty())
@ -805,9 +849,88 @@ proc nimTypeOf(loc: Location; known: var TypeTable; name: string; orDef: Definit
newEmpty(),
nkRecList.newTree(recCase))
proc nimTypeOf(loc: Location; known: var TypeTable; name: string; def: DefinitionAnd): TypeSpec =
proc isAny(sp: SimplePattern): bool =
sp.orKind == SimplePatternKind.any
proc initSimpleAny: SimplePattern =
SimplePattern(orKind: SimplePatternKind.any)
proc asAny(nsp: NamedSimplePattern): NamedSimplePattern =
result = nsp
case result.orKind
of NamedSimplePatternKind.named:
if not result.named.pattern.isAny:
result.named.pattern = initSimpleAny()
of NamedSimplePatternKind.anonymous:
if not result.anonymous.isAny:
result.anonymous = initSimpleAny()
type
AndEntry = tuple[pattern: NamedSimplePattern, optional: bool]
AndEntries = OrderedTable[Value, AndEntry]
proc collect(entries: var AndEntries; loc: Location; def: Definition; optional: bool) {.gcsafe.}
proc collect(entries: var AndEntries; loc: Location; pat: SimplePattern; optional: bool) =
case pat.orKind
of SimplePatternKind.Ref:
let (loc, def) = deref(loc, pat.ref)
collect(entries, loc, def, optional)
else:
raiseAssert "cannot collect dictionary entries from " & $pat
proc collect(entries: var AndEntries; loc: Location; comp: CompoundPattern; optional: bool) =
case comp.orKind
of CompoundPatternKind.dict:
for key, nsp in comp.dict.entries.pairs:
if entries.hasKey(key):
entries[key] = (asAny nsp, optional)
else:
entries[key] = (nsp, optional)
else:
raiseAssert "cannot collect dictionary entries from " & $comp
proc collect(entries: var AndEntries; loc: Location; pat: Pattern; optional: bool) =
case pat.orKind
of PatternKind.SimplePattern:
collect(entries, loc, pat.simplepattern, optional)
of PatternKind.CompoundPattern:
collect(entries, loc, pat.compoundpattern, optional)
proc collect(entries: var AndEntries; loc: Location; def: Definition; optional: bool) =
case def.orKind
of DefinitionKind.or:
collect(entries, loc, def.or.field0.pattern0.pattern, true)
collect(entries, loc, def.or.field0.pattern1.pattern, true)
for np in def.or.field0.patternN:
collect(entries, loc, np.pattern, true)
of DefinitionKind.and:
collect(entries, loc, def.and.field0.pattern0.pattern, optional)
collect(entries, loc, def.and.field0.pattern1.pattern, optional)
for np in def.and.field0.patternN:
collect(entries, loc, np.pattern, optional)
of DefinitionKind.Pattern:
collect(entries, loc, def.pattern, optional)
proc toDef(a: DefinitionAnd): Definition =
Definition(orKind: DefinitionKind.and, `and`: a)
proc nimTypeOf(loc: Location; known: var TypeTable; name: string; andDef: DefinitionAnd): TypeSpec =
var def = andDef.toDef
if isDictionary(loc, def):
result.node = nkBracketExpr.newTree(ident"Table", ident"Symbol", ident"Value")
var
recList = nkRecList.newNode
entries: AndEntries
collect(entries, loc, def, false)
sort(entries) do (x, y: (Value, AndEntry)) -> int:
preserves.cmp(x[0], y[0])
# TODO: sort the entries
var i = 0
for key, (nsp, opt) in entries.pairs:
recList.addField(loc, known, name, nsp, i, opt)
inc(i)
result.node = nkObjectTy.newTree(
newEmpty(), newEmpty(), recList)
else:
result.node = ident"Value"
@ -869,7 +992,7 @@ proc collectRefImports(imports: var StringSet; loc: Location; def: Definition) =
collectRefImports(imports, loc, na.pattern)
of DefinitionKind.and:
if isDictionary(loc, def):
incl(imports, "std/tables")
incl(imports, "std/options")
collectRefImports(imports, loc, def.and.field0.pattern0.pattern)
collectRefImports(imports, loc, def.and.field0.pattern1.pattern)
for np in def.and.field0.patternN:
@ -906,7 +1029,7 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
else:
var name = string name
name[0] = name[0].toUpperAscii
var defIdent = ident(name)
var defIdent = parameterize(loc, ident(name), isEmbedded(loc, def))
if not isSymbolEnum(loc, def) and not isAny(loc, def):
if isEmbedded(loc, def):
mergeType(embeddableType, defIdent)
@ -928,7 +1051,12 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
for module in importSet:
add(imports, ident(module))
if not embeddableType.isNil:
let genericParams = newEmpty()
let genericParams =
if scm.hasEmbeddedType:
nkGenericParams.newTree(
nkIdentDefs.newTree(
embeddedIdent(scm), newEmpty(), newEmpty()))
else: newEmpty()
procs.add nkProcDef.newTree(
"$".toFieldIdent,
newEmpty(),

View File

@ -152,7 +152,7 @@ proc writeText*(stream: Stream; pr: Value; mode = textPreserves) =
writeText(stream, value, mode)
write(stream, '}')
of pkEmbedded:
write(stream, "#!")
if not pr.embedded: write(stream, "#!")
if pr.embeddedRef.isNil:
write(stream, "<null>")
else:

View File

@ -17,11 +17,13 @@ suite "conversions":
a: int
b: seq[int]
c {.preservesEmbedded.}: Bar
d: Option[bool]
e: Option[bool]
let
c = Foobar(a: 1, b: @[2], c: ("ku", ))
c = Foobar(a: 1, b: @[2], c: ("ku", ), e: some(true))
b = toPreserves(c)
a = preservesTo(b, Foobar)
check($b == """{a: 1 b: [2] c: #!["ku"]}""")
check($b == """{a: 1 b: [2] c: #!["ku"] e: #t}""")
check(a.isSome)
if a.isSome: check(get(a) == c)
check(b.kind == pkDictionary)
@ -96,3 +98,7 @@ suite "toPreserve":
test s: check($p == s)
check false.toPreserves, "#f"
check [0, 1, 2, 3].toPreserves, "[0 1 2 3]"
test "toRecord":
let r = toRecord(Symbol"foo", "üks", "kaks", "kolm", {4..7})
check $r == """<foo "üks" "kaks" "kolm" #{4 5 6 7}>"""