Schemas: use canonical meta-schema

This commit is contained in:
Emery Hemingway 2021-09-27 15:58:36 +02:00
parent 220577c8a0
commit 99201de724
9 changed files with 1039 additions and 1181 deletions

2
.gitignore vendored
View File

@ -1,7 +1,9 @@
tests/test_integers
tests/test_parser
tests/test_rfc8259
tests/test_schemas
preserves_encode
preserves_decode
preserves_from_json
preserves_to_json
preserves_schema_nim

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "upstream"]
path = upstream
url = https://gitlab.com/preserves/preserves.git

View File

@ -6,7 +6,7 @@ description = "data model and serialization format"
license = "Unlicense"
srcDir = "src"
bin = @["preserves/private/preserves_schema_nim", "preserves/private/preserves_encode"]
bin = @["preserves/preserves_schema_nim", "preserves/private/preserves_encode"]
# Dependencies

View File

@ -0,0 +1,796 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
## This module implements Nim code generation from Preserves schemas.
# This module imports code that it generates! After making any changes here
# the schema module must be regenerated!
# nim c -r ./preserves_schema_nim ../../upstream/schema/schema.bin
import std/[hashes, sequtils, strutils, sets, tables]
import compiler/[ast, idents, renderer, lineinfos]
import ../preserves, ./schema, ./parse
type
TypeSpec = tuple[node: PNode, embeddable: bool]
TypeTable = OrderedTable[string, PNode]
proc add(parent, child: PNode): PNode {.discardable.} =
parent.sons.add child
parent
proc add(parent: PNode; children: varargs[PNode]): PNode {.discardable.} =
parent.sons.add children
parent
proc nn(kind: TNodeKind; children: varargs[PNode]): PNode =
result = newNode(kind)
result.sons.add(children)
proc nn(kind: TNodeKind; child: PNode): PNode =
result = newNode(kind)
result.sons.add(child)
proc ident(s: string): PNode =
newIdentNode(PIdent(s: s), TLineInfo())
proc accQuote(n: PNode): Pnode =
nkAccQuoted.newNode.add(n)
proc pattern(np: NamedPattern): Pattern =
case np.orKind
of NamedPatternKind.named:
Pattern(orKind: PatternKind.SimplePattern, simplePattern: np.named.pattern)
of NamedPatternKind.anonymous:
np.anonymous
proc pattern(np: NamedSimplePattern): SimplePattern =
case np.orKind
of NamedSimplePatternKind.named:
np.named.pattern
of NamedSimplePatternKind.anonymous:
np.anonymous
proc ident(sp: SimplePattern): PNode =
raiseAssert "need ident from " & $sp
proc ident(cp: CompoundPattern; fallback: string): PNode =
case cp.orKind
of CompoundPatternKind.`rec`:
ident($cp.rec.label)
of CompoundPatternKind.`tuple`,
CompoundPatternKind.`tuplePrefix`,
CompoundPatternKind.`dict`:
ident(fallback)
proc ident(pat: Pattern; fallback = string): PNode =
case pat.orKind
of PatternKind.simplePattern:
ident(pat.simplePattern, fallback)
of PatternKind.compoundPattern:
ident(pat.compoundPattern, fallback)
proc ident(np: NamedPattern; fallback: string): PNode =
case np.orKind
of NamedPatternKind.`named`:
ident(np.named.name)
of NamedPatternKind.`anonymous`:
ident(fallback)
proc ident(np: NamedSimplePattern; fallback: string): PNode =
case np.orKind
of NamedSimplePatternKind.`named`:
ident(np.named.name)
of NamedSimplePatternKind.`anonymous`:
ident(fallback)
proc parameterize(node: PNode; embeddable: bool): PNode =
if embeddable and node.kind notin {nkBracketExpr}:
nn(nkBracketExpr, node, ident"E")
else: node
proc parameterize(spec: TypeSpec): PNode =
parameterize(spec.node, spec.embeddable)
proc isPreserve(n: PNode): bool =
n.kind == nkBracketExpr and
n.renderTree == "Preserve[E]"
proc orEmbed(x: var TypeSpec; y: TypeSpec) =
x.embeddable = x.embeddable or y.embeddable
proc dotExtend(result: var PNode; label: string) =
var id = ident(label)
if result.isNil: result = id
else: result = nn(nkDotExpr, result, id)
proc ident(`ref`: Ref): PNode =
for m in`ref`.module: dotExtend(result, m)
dotExtend(result, `ref`.name.capitalizeAscii)
proc deref(scm: Schema; r: Ref): Definition =
assert r.module == @[]
scm.data.definitions[r.name]
proc isEmbeddable(scm: Schema): bool =
scm.data.embeddedType.orKind == EmbeddedtypenameKind.`Ref`
proc preserveIdent(scm: Schema): Pnode =
nn(nkBracketExpr, ident"Preserve", ident(if scm.isEmbeddable: "E" else: "void"))
proc embeddedIdent(scm: Schema): PNode =
case scm.data.embeddedType.orKind
of EmbeddedtypenameKind.`false`: ident"void"
of EmbeddedtypenameKind.`Ref`: preserveIdent(scm)
proc hash(r: Ref): Hash = r.toPreserve.hash
type RefSet = HashSet[Ref]
proc isEmbeddable(scm: Schema; pat: Pattern; seen: RefSet): bool {.gcsafe.}
proc isEmbeddable(scm: Schema; def: Definition; seen: RefSet): bool {.gcsafe.}
proc isEmbeddable(scm: Schema; sp: SimplePattern; seen: RefSet): bool =
if not scm.isEmbeddable: false
else:
case sp.orKind
of SimplepatternKind.`atom`, SimplepatternKind.`lit`: false
of SimplepatternKind.`any`: true
of SimplepatternKind.`embedded`: true
of SimplepatternKind.`seqof`:
isEmbeddable(scm, sp.seqof.pattern, seen)
of SimplepatternKind.`setof`:
isEmbeddable(scm, sp.setof.pattern, seen)
of SimplepatternKind.`dictof`:
isEmbeddable(scm, sp.dictof.key, seen) or
isEmbeddable(scm, sp.dictof.value, seen)
of SimplepatternKind.`Ref`:
if sp.ref.module != @[]: true
else:
if sp.ref in seen: false
else:
var seen = seen
seen.incl sp.ref
isEmbeddable(scm, deref(scm, sp.ref), seen)
proc isEmbeddable(scm: Schema; np: NamedSimplePattern; seen: RefSet): bool =
case np.orKind
of NamedSimplePatternKind.`named`:
isEmbeddable(scm, np.named.pattern, seen)
of NamedSimplePatternKind.`anonymous`:
isEmbeddable(scm, np.anonymous, seen)
proc isEmbeddable(scm: Schema; cp: CompoundPattern; seen: RefSet): bool =
if not scm.isEmbeddable: false
else:
case cp.orKind
of CompoundPatternKind.`rec`:
isEmbeddable(scm, cp.rec.label.pattern, seen) or
isEmbeddable(scm, cp.rec.fields.pattern, seen)
of CompoundPatternKind.`tuple`:
any(cp.tuple.patterns) do (np: NamedPattern) -> bool:
isEmbeddable(scm, np.pattern, seen)
of CompoundPatternKind.`tupleprefix`:
proc pred(np: NamedPattern): bool =
isEmbeddable(scm, np.pattern, seen)
isEmbeddable(scm, cp.tupleprefix.variable, seen) or
any(cp.tupleprefix.fixed, pred)
of CompoundPatternKind.`dict`:
true # the key type is `Preserve`
proc isEmbeddable(scm: Schema; pat: Pattern; seen: RefSet): bool =
case pat.orKind
of PatternKind.`SimplePattern`:
isEmbeddable(scm, pat.simplePattern, seen)
of PatternKind.`CompoundPattern`:
isEmbeddable(scm, pat.compoundPattern, seen)
proc isEmbeddable(scm: Schema; def: Definition; seen: RefSet): bool =
if not scm.isEmbeddable: false
else:
case def.orKind
of DefinitionKind.`or`:
proc isEmbeddable(na: NamedAlternative): bool =
isEmbeddable(scm, na.pattern, seen)
isEmbeddable(def.or.data.pattern0) or
isEmbeddable(def.or.data.pattern1) or
any(def.or.data.patternN, isEmbeddable)
of DefinitionKind.`and`:
proc isEmbeddable(np: NamedPattern): bool =
isEmbeddable(scm, np.pattern, seen)
isEmbeddable(def.and.data.pattern0) or
isEmbeddable(def.and.data.pattern1) or
any(def.and.data.patternN, isEmbeddable)
of DefinitionKind.`Pattern`:
isEmbeddable(scm, def.pattern, seen)
proc isEmbeddable(scm: Schema; p: Definition|Pattern|SimplePattern): bool =
var seen: RefSet
isEmbeddable(scm, p, seen)
proc isLiteral(scm: Schema; def: Definition): bool {.gcsafe.}
proc isLiteral(scm: Schema; sp: SimplePattern): bool =
case sp.orKind
of SimplepatternKind.`Ref`:
if sp.ref.module.len == 0:
result = isLiteral(scm, deref(scm, sp.ref))
of SimplepatternKind.lit:
result = true
else: discard
proc isLiteral(scm: Schema; pat: Pattern): bool =
case pat.orKind
of PatternKind.SimplePattern:
isLiteral(scm, pat.simplePattern)
of PatternKind.CompoundPattern:
false # TODO it could be a compound of all literals
proc isLiteral(scm: Schema; def: Definition): bool =
if def.orKind == DefinitionKind.Pattern:
result = isLiteral(scm, def.pattern)
proc isRef(sp: SimplePattern): bool =
sp.orKind == SimplePatternKind.`Ref`
proc isRef(pat: Pattern): bool =
pat.orKind == PatternKind.SimplePattern and
pat.simplePattern.isRef
proc isSimple(pat: Pattern): bool =
pat.orKind == PatternKind.SimplePattern
proc isSymbolEnum(scm: Schema; orDef: DefinitionOr): bool =
proc isLiteral(na: NamedAlternative): bool = isLiteral(scm, na.pattern)
result = isLiteral(orDef.data.pattern0) and isLiteral(orDef.data.pattern1)
for na in orDef.data.patternN:
if not result: break
result = isLiteral(na)
proc isSymbolEnum(scm: Schema; def: Definition): bool =
case def.orKind
of DefinitionKind.Pattern:
if def.pattern.orKind == PatternKind.SimplePattern and
def.pattern.simplePattern.orKind == SimplepatternKind.`Ref` and
def.pattern.simplePattern.ref.module.len == 0:
result = isSymbolEnum(scm, deref(scm, def.pattern.simplePattern.ref))
# TODO: no need to de-ref this
of DefinitionKind.or:
result = isSymbolEnum(scm, def.or)
else: discard
proc typeIdent(atom: AtomKind): PNode =
case atom
of AtomKind.`Boolean`: ident"bool"
of AtomKind.`Float`: ident"float32"
of AtomKind.`Double`: ident"float64"
of AtomKind.`Signedinteger`: ident"BiggestInt"
of AtomKind.`String`: ident"string"
of AtomKind.`Bytestring`: nn(nkBracketExpr, ident"seq", ident"byte")
of AtomKind.`Symbol`: ident"string"
proc typeIdent(scm: Schema; sp: SimplePattern): TypeSpec =
case sp.orKind
of SimplepatternKind.`atom`:
result = (typeIdent(sp.atom.atomKind), false)
of SimplepatternKind.`embedded`:
result = (scm.embeddedIdent, scm.isEmbeddable)
of SimplepatternKind.`seqof`:
result = typeIdent(scm, sp.seqof.pattern)
result.node = nn(nkBracketExpr, ident"seq", result.node)
of SimplepatternKind.`setof`:
result = typeIdent(scm, sp.setof.pattern)
result.node = nn(nkBracketExpr, ident"HashSet", result.node)
of SimplepatternKind.`dictof`:
let
key = typeIdent(scm, sp.dictof.key)
val = typeIdent(scm, sp.dictof.value)
result.node = nn(nkBracketExpr, ident"Table", key.node, val.node)
result.embeddable = key.embeddable or val.embeddable
of SimplepatternKind.`Ref`:
result = (ident(sp.ref), isEmbeddable(scm, sp))
result.node = parameterize(result)
else:
result = (preserveIdent(scm), isEmbeddable(scm))
proc typeIdent(scm: Schema; pat: Pattern): TypeSpec =
case pat.orKind
of PatternKind.SimplePattern: typeIdent(scm, pat.simplePattern)
else: raiseAssert "no typeIdent for " & $pat
proc toExport(n: sink PNode): PNode =
nkPostFix.newNode.add(ident"*", n)
proc toStrLit(scm: Schema; sp: SimplePattern): PNode =
case sp.orKind
of SimplePatternKind.`lit`:
result = PNode(kind: nkStrLit, strVal: $sp.lit.value)
of SimplePatternKind.`Ref`:
let def = deref(scm, sp.ref)
result = toStrLit(scm, def.pattern.simplePattern)
else: assert false
proc toFieldIdent(s: string): PNode =
nn(nkPostFix, ident("*"), nn(nkAccQuoted, ident(s)))
proc toFieldIdent(scm: Schema, label: string; pat: Pattern): PNode =
result = label.toFieldIdent
if isLiteral(scm, pat):
result = nn(nkPragmaExpr,
result,
nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesLiteral",
toStrLit(scm, pat.simplePattern))))
proc newEmpty(): PNode = newNode(nkEmpty)
#[
proc literal(scm: Schema; sn: SchemaNode): Value =
case sn.orKind
of snkLiteral: result = sn.value
of snkRef:
if sn.refPath.len == 1:
result = literal(scm, scm.definitions[sn.refPath[0]])
else:
raiseAssert("not convertable to a literal: " & $sn)
else:
raiseAssert("not convertable to a literal: " & $sn)
]#
proc embeddingParams(embeddable: bool): PNode =
if embeddable:
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void"))
else:
newEmpty()
proc identDef(a, b: PNode; embeddable: bool): PNode =
if embeddable and b.kind notin {nkBracketExpr, nkTupleTy}:
# TODO: probably not a sufficient check
nn(nkIdentDefs, a, nn(nkBracketExpr, b, ident"E"), newEmpty())
else:
nn(nkIdentDefs, a, b, newEmpty())
proc identDef(l: PNode; ts: TypeSpec): PNode =
identDef(l, ts.node, ts.embeddable)
proc label(pat: Pattern): string =
raiseAssert "need to derive record label for " & $pat
proc label(na: NamedPattern): string =
case na.orKind
of NamedPatternKind.`named`:
na.named.name
of NamedPatternKind.`anonymous`:
"data" # TODO
proc idStr(sp: SimplePattern): string =
if sp.orKind == SimplepatternKind.lit:
case sp.lit.value.kind
of pkString:
result = sp.lit.value.string
of pkSymbol:
result = sp.lit.value.symbol
else: discard
doAssert(result != "", "no idStr for " & $sp)
proc idStr(pat: Pattern): string =
doAssert(pat.orKind == PatternKind.SimplePattern)
pat.simplePattern.idStr
proc idStr(np: NamedPattern): string =
case np.orKind
of NamedPatternKind.`named`:
np.named.name
of NamedPatternKind.`anonymous`:
np.anonymous.idStr
proc typeDef(scm: Schema; name: string; pat: Pattern; ty: PNode): PNode =
let
embedParams = embeddingParams(isEmbeddable(scm, pat))
id = name.ident.toExport
if pat.orKind == PatternKind.`CompoundPattern`:
case pat.compoundPattern.orKind
of CompoundPatternKind.`rec`:
nn(nkTypeDef,
nn(nkPragmaExpr,
id, nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesRecord",
PNode(kind: nkStrLit, strVal: pat.compoundPattern.rec.label.idStr)))),
embedParams,
ty)
of CompoundPatternKind.`tuple`, CompoundPatternKind.`tuplePrefix`:
nn(nkTypeDef,
nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesTuple")),
embedParams,
ty)
of CompoundPatternKind.`dict`:
nn(nkTypeDef,
nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesDictionary")),
embedParams,
ty)
else:
nn(nkTypeDef, name.ident.toExport, embedParams, ty)
proc typeDef(scm: Schema; name: string; def: Definition; ty: PNode): PNode =
case def.orKind
of DefinitionKind.or:
nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.accQuote.toExport,
nn(nkPragma, ident"preservesOr")),
embeddingParams(isEmbeddable(scm, def)),
ty)
of DefinitionKind.and:
raiseAssert "And variants not suported"
of DefinitionKind.Pattern:
typeDef(scm, name, def.pattern, ty)
proc nimTypeOf(scm: Schema; known: var TypeTable; nsp: NamedSimplePattern; name = ""): TypeSpec
proc nimTypeOf(scm: Schema; known: var TypeTable; pat: Pattern; name = ""): TypeSpec
proc nimTypeOf(scm: Schema; known: var TypeTable; cp: CompoundPattern; name = ""): TypeSpec
proc nimTypeOf(scm: Schema; known: var TypeTable; sp: SimplePattern; name = ""): TypeSpec =
typeIdent(scm, sp)
proc addField(recList: PNode; scm: Schema; known: var TypeTable; sp: SimplePattern; label: string): PNode {.discardable.} =
let id = label.toFieldIdent
if isLiteral(scm, sp):
let id = nn(nkPragmaExpr,
id,
nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesLiteral",
toStrLit(scm, sp))))
recList.add identDef(id, (ident"bool", false))
elif sp.orKind == SimplePatternKind.`atom` and
sp.atom.atomKind == AtomKind.Symbol:
let id = nn(nkPragmaExpr,
id, nn(nkPragma, ident"preservesSymbol"))
recList.add identDef(id, (ident"string", false))
else:
recList.add identDef(id, nimTypeOf(scm, known, sp))
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; cp: CompoundPattern; parentName: string): PNode {.discardable.} =
case cp.orKind
of CompoundPatternKind.rec:
# recList.add identDef(ident(label), nimTypeOf(scm, known, cp, ""))
raiseassert "unexpected record of fields " & $cp.rec
of CompoundPatternKind.tuple:
for np in cp.tuple.patterns:
let
label = np.label
id = label.toFieldIdent
if np.pattern.isRef or np.pattern.isSimple:
addField(recList, scm, known, np.pattern.simplePattern, label)
else:
var
pat = np.pattern
typeName = parentName & capitalizeAscii(label)
fieldSpec = nimTypeOf(scm, known, pat, label)
known[typeName] = typeDef(scm, typeName, pat, fieldSpec.node)
recList.add identDef(id, ident(typeName), fieldSpec.embeddable)
else: raiseAssert "not adding fields for " & $cp
reclist
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; pat: Pattern; parentName: string): PNode {.discardable.} =
case pat.orKind
of PatternKind.SimplePattern:
raiseAssert "addFields called with SimplePattern " & $pat.simplePattern
# addField(recList, scm, known, pat.simplePattern, "data")
of PatternKind.CompoundPattern:
discard addFields(recList, scm, known, pat.compoundPattern, parentName)
reclist
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} =
for key, val in entries.pairs:
doAssert(key.isSymbol)
let label = key.symbol
addField(recList, scm, known, val.pattern, label)
recList
proc nimTypeOf(scm: Schema; known: var TypeTable; nsp: NamedSimplePattern; name: string): TypeSpec =
case nsp.orKind
of NamedsimplepatternKind.named:
nimTypeOf(scm, known, nsp.named.pattern, nsp.named.name)
of NamedsimplepatternKind.anonymous:
nimTypeOf(scm, known, nsp.anonymous, name)
proc nimTypeOf(scm: Schema; known: var TypeTable; cp: CompoundPattern; name: string): TypeSpec =
case cp.orKind
of CompoundPatternKind.`rec`:
result.node = nn(nkObjectTy,
newEmpty(), newEmpty(),
nn(nkRecList).addFields(scm, known, cp.rec.fields.pattern, name))
of CompoundPatternKind.`tuple`:
var recList = nn(nkRecList)
for np in cp.tuple.patterns:
let pat = np.pattern
if not isLiteral(scm, pat):
let fieldType = nimTypeOf(scm, known, pat)
orEmbed result, fieldType
recList.add identDef(ident(np, name).accQuote.toExport, fieldType)
result.node = nn(nkObjectTy, newEmpty(), newEmpty(), recList)
of CompoundPatternKind.`tupleprefix`:
var recList = nn(nkRecList)
for np in cp.tuplePrefix.fixed:
let pat = np.pattern
if not isLiteral(scm, pat):
let fieldType = nimTypeOf(scm, known, pat)
orEmbed result, fieldType
recList.add identDef(ident(np, name).accQuote.toExport, fieldType)
let variableType = nimTypeOf(scm, known, cp.tuplePrefix.variable)
recList.add identDef(
nn(nkPragmaExpr,
ident(cp.tuplePrefix.variable, name).accQuote.toExport,
nn(nkPragma, ident"preservesTupleTail")),
variableType.parameterize,
variableType.embeddable)
result.node = nn(nkObjectTy, newEmpty(), newEmpty(), recList)
of CompoundPatternKind.`dict`:
result.node = nn(nkObjectTy, newEmpty(), newEmpty(),
nn(nkRecList).addFields(scm, known, cp.dict.entries, name))
proc nimTypeOf(scm: Schema; known: var TypeTable; pat: Pattern; name: string): TypeSpec =
case pat.orKind
of PatternKind.SimplePattern:
nimTypeOf(scm, known, pat.simplePattern, name)
of PatternKind.CompoundPattern:
nimTypeOf(scm, known, pat.compoundPattern, name)
proc nimTypeOf(scm: Schema; known: var TypeTable; orDef: DefinitionOr; name: string): TypeSpec =
proc toEnumTy(): PNode =
let ty = nkEnumTy.newNode.add newEmpty()
proc add (na: NamedAlternative) =
ty.add na.variantLabel.ident.accQuote
add(orDef.data.pattern0)
add(orDef.data.pattern1)
for na in orDef.data.patternN:
add(na)
ty
if isSymbolEnum(scm, orDef):
result.node = toEnumTy()
else:
let
enumName = name & "Kind"
enumIdent = ident(enumName)
if enumName notin known:
known[enumName] = nn(nkTypeDef,
nn(nkPragmaExpr,
enumName.ident.toExport,
nn(nkPragma, ident"pure")),
newEmpty(),
toEnumTy())
let recCase = nkRecCase.newNode.add(
nkIdentDefs.newNode.add(
"orKind".ident.toExport,
enumName.ident,
newEmpty()))
template addCase(na: NamedAlternative) =
let branchRecList = nn(nkRecList)
var memberType: TypeSpec
if isLiteral(scm, na.pattern):
memberType.node = ident"bool"
elif na.pattern.isRef:
memberType = typeIdent(scm, na.pattern)
else:
let memberTypeName = name & na.variantLabel.capitalizeAscii
memberType.node = ident memberTypeName
let ty = nimTypeOf(scm, known, na.pattern, memberTypeName)
orEmbed memberType, ty
if memberTypeName notin known and not isLiteral(scm, na.pattern):
known[memberTypeName] =
typeDef(scm, memberTypeName, na.pattern, ty.node)
orEmbed result, memberType
branchRecList.add nn(nkIdentDefs,
toFieldIdent(scm, na.variantLabel.normalize, na.pattern),
memberType.node, newEmpty())
recCase.add nn(nkOfBranch,
nn(nkDotExpr,
enumIdent, na.variantLabel.ident.accQuote),
branchRecList)
addCase(orDef.data.pattern0)
addCase(orDef.data.pattern1)
for na in orDef.data.patternN: addCase(na)
result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(),
newEmpty(),
nn(nkRecList, recCase)))
proc nimTypeOf(scm: Schema; known: var TypeTable; def: Definition; name: string): TypeSpec =
case def.orKind
of DefinitionKind.or:
nimTypeOf(scm, known, def.or, name)
of DefinitionKind.and: raiseAssert "And definitions are unsupported"
of DefinitionKind.Pattern:
nimTypeOf(scm, known, def.pattern, name)
proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: Definition) =
discard
proc literalToPreserveCall(scm: Schema; pr: Preserve): PNode =
var prConstr = nn(nkObjConstr, preserveIdent(scm))
proc constr(kind, field: string; lit: PNode) =
prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind))
prConstr.add nn(nkExprColonExpr, ident(field), lit)
case pr.orKind
of pkBoolean:
constr($pr.orKind, "bool", if pr.bool: ident"true" else: ident"false")
of pkFloat:
constr($pr.orKind, "float", newFloatNode(nkFloat32Lit, pr.float))
of pkDouble:
constr($pr.orKind, "double", newFloatNode(nkFloat64Lit, pr.double))
of pkSignedInteger:
constr($pr.orKind, "int", newIntNode(nkInt64Lit, pr.int))
of pkString:
constr($pr.orKind, "string", newStrNode(nkTripleStrLit, pr.string))
of pkByteString:
return nn(nkCall, ident"parsePreserves", newStrNode(nkTripleStrLit, $pr))
of pkSymbol:
constr($pr.orKind, "symbol", newStrNode(nkStrLit, pr.symbol))
else:
raise newException(ValueError, "refusing to convert to a literal: " & $pr)
prConstr
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; pat: Pattern) =
discard
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; def: Definition) =
discard
proc collectRefImports(imports: PNode; pat: Pattern)
proc collectRefImports(imports: PNode; sp: SimplePattern) =
case sp.orKind
of SimplePatternKind.dictof:
imports.add ident"std/tables"
of SimplePatternKind.Ref:
if sp.`ref`.module != @[]:
imports.add ident(sp.ref.module[0])
else: discard
proc collectRefImports(imports: PNode; cp: CompoundPattern) =
case cp.orKind
of CompoundPatternKind.`rec`:
collectRefImports(imports, cp.rec.label.pattern)
collectRefImports(imports, cp.rec.fields.pattern)
of CompoundPatternKind.`tuple`:
for p in cp.tuple.patterns: collectRefImports(imports, p.pattern)
of CompoundPatternKind.`tupleprefix`:
for np in cp.tupleprefix.fixed: collectRefImports(imports, np.pattern)
collectRefImports(imports, cp.tupleprefix.variable.pattern)
of CompoundPatternKind.`dict`:
for nsp in cp.dict.entries.values:
collectRefImports(imports, nsp.pattern)
proc collectRefImports(imports: PNode; pat: Pattern) =
case pat.orKind
of PatternKind.SimplePattern:
collectRefImports(imports, pat.simplePattern)
of PatternKind.CompoundPattern:
collectRefImports(imports, pat.compoundPattern)
proc collectRefImports(imports: PNode; def: Definition) =
case def.orKind
of DefinitionKind.`or`:
collectRefImports(imports, def.or.data.pattern0.pattern)
collectRefImports(imports, def.or.data.pattern1.pattern)
for na in def.or.data.patternN:
collectRefImports(imports, na.pattern)
of DefinitionKind.`and`:
collectRefImports(imports, def.and.data.pattern0.pattern)
collectRefImports(imports, def.and.data.pattern1.pattern)
for np in def.and.data.patternN:
collectRefImports(imports, np.pattern)
of DefinitionKind.Pattern:
collectRefImports(imports, def.pattern)
proc collectRefImports(imports: PNode; scm: Schema) =
for _, def in scm.data.definitions:
collectRefImports(imports, def)
proc renderNimModule*(scm: Schema): string =
## Construct and render a Nim module from a `Schema`.
var
typeDefs: TypeTable
typeSection = newNode nkTypeSection
procs: seq[PNode]
megaType: PNode
for name, def in scm.data.definitions.pairs:
if isLiteral(scm, def):
generateConstProcs(procs, scm, name, def)
else:
var name = name
name[0] = name[0].toUpperAscii
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
if not isSymbolEnum(scm, def):
if megaType.isNil:
megaType = defIdent
else:
megaType = nn(nkInfix,
ident"|", megaType, defIdent)
let typeSpec = nimTypeOf(scm, typeDefs, def, name)
typeDefs[name] = typeDef(scm, name, def, typeSpec.node)
generateProcs(procs, scm, name, def)
for td in typeDefs.values:
typeSection.add td
var imports = nkImportStmt.newNode.add(
ident"std/typetraits",
ident"preserves")
collectRefImports(imports, scm)
let dollarGenericParams =
if isEmbeddable(scm):
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty()))
else: newEmpty()
procs.add nn(nkProcDef,
"$".toFieldIdent,
newEmpty(),
dollarGenericParams,
nn(nkFormalParams,
ident"string",
nn(nkIdentDefs,
ident"x",
megaType,
newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"$",
nn(nkCall, ident"toPreserve", ident"x"))))
procs.add nn(nkProcDef,
"encode".ident.toExport,
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams,
nn(nkBracketExpr, ident"seq", ident"byte"),
nn(nkIdentDefs,
ident"x",
megaType,
newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"encode", nn(nkCall,
ident"toPreserve", ident"x", ident"E"))))
var module = newNode(nkStmtList).add(
imports,
typeSection
).add(procs)
renderTree(module, {renderNone, renderIr})
when isMainModule:
proc writeModule(scm: Schema; path: string) =
let txt = renderNimModule(scm)
writeFile(path, txt)
stdout.writeLine(path)
import std/[options, os, parseopt]
var inputs: seq[string]
for kind, key, val in getopt():
case kind
of cmdLongOption:
case key
else: quit("unhandled option " & key)
of cmdShortOption:
case key
else: quit("unhandled option " & key)
of cmdArgument:
inputs.add absolutePath(key)
of cmdEnd: discard
for filepath in inputs:
var useful: bool
let pr = decodePreserves(readFile filepath)
preserveTo(pr, Schema).map do (scm: Schema):
useful = true
let
(_, name, _) = splitFile(filepath)
outputPath = name & ".nim"
writeModule(scm, outputPath)
preserveTo(pr, Bundle).map do (bundle: Bundle):
useful = true
for modPath, scm in bundle.modules:
let path = joinPath(modPath) & ".nim"
writeModule(scm, path)
if not useful:
quit "Failed to recognized " & filepath

View File

@ -1,730 +0,0 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[os, strutils, sets, tables]
import compiler/[ast, idents, renderer, lineinfos]
import ../../preserves, ../schemas
type
Value = Preserve[void]
TypeSpec = tuple[node: PNode, embeddable: bool]
TypeTable = OrderedTable[string, TypeSpec]
proc add(parent, child: PNode): PNode {.discardable.} =
parent.sons.add child
parent
proc add(parent: PNode; children: varargs[PNode]): PNode {.discardable.} =
parent.sons.add children
parent
proc child(sn: SchemaNode): SchemaNode =
assert(sn.nodes.len == 1)
sn.nodes[0]
proc nn(kind: TNodeKind; children: varargs[PNode]): PNode =
result = newNode(kind)
result.sons.add(children)
proc nn(kind: TNodeKind; child: PNode): PNode =
result = newNode(kind)
result.sons.add(child)
proc ident(s: string): PNode =
newIdentNode(PIdent(s: s), TLineInfo())
proc accQuote(n: PNode): Pnode =
nkAccQuoted.newNode.add(n)
proc ident(sn: SchemaNode): PNode =
var s: string
case sn.kind
of snkAlt:
s = sn.altLabel.nimIdentNormalize
s[0] = s[0].toLowerAscii
of snkLiteral: s = $sn.value
of snkRecord:
s = sn.nodes[0].value.symbol
of snkNamed:
s = sn.name
of snkDictionary, snkVariableTuple:
s = "data"
else:
raiseAssert("no ident for " & $sn.kind & " " & $sn)
s.ident.accQuote
proc parameterize(node: PNode; embeddable: bool): PNode =
if embeddable: nn(nkBracketExpr, node, ident"E")
else: node
proc parameterize(spec: TypeSpec): PNode =
parameterize(spec.node, spec.embeddable)
proc isPreserve(n: PNode): bool =
n.kind == nkBracketExpr and
n.renderTree == "Preserve[E]"
proc preserveIdent(): Pnode =
nn(nkBracketExpr, ident"Preserve", ident"E")
proc orEmbed(x: var TypeSpec; y: TypeSpec) =
x.embeddable = x.embeddable or y.embeddable
proc isEmbeddable(scm: Schema; sn: SchemaNode; seen: var HashSet[string]): bool =
case sn.kind
of snkAtom, snkLiteral: discard
of snkAlt:
result = isEmbeddable(scm, sn.altBranch, seen)
of snkAny: result = true
of snkRef:
if sn.refPath.len == 1:
let name = sn.refPath[0]
if name notin seen: # loop detection
seen.incl name
result = isEmbeddable(scm, scm.definitions[name], seen)
else:
result = false
# TODO: cross-module types
of snkEmbedded:
result = isEmbeddable(scm, sn.embed, seen)
of snkNamed:
result = isEmbeddable(scm, sn.pattern, seen)
else:
for bn in sn.nodes:
result = isEmbeddable(scm, bn, seen)
if result: break
proc isEmbeddable(scm: Schema; sn: SchemaNode): bool =
var seen: HashSet[string]
isEmbeddable(scm, sn, seen)
proc isConst(scm: Schema; sn: SchemaNode): bool =
case sn.kind
of snkLiteral: result = true
of snkRef:
if sn.refPath.len == 1:
result = isConst(scm, scm.definitions[sn.refPath[0]])
else: discard
proc isSymbolEnum(scm: Schema; sn: SchemaNode): bool =
case sn.kind
of snkRef:
if sn.refPath.len == 1:
result = isSymbolEnum(scm, scm.definitions[sn.refPath[0]])
of snkOr:
for bn in sn.nodes:
if bn.altBranch.kind != snkLiteral or
bn.altBranch.value.kind != pkSymbol:
return false
result = true
else: discard
proc typeIdent(scm: Schema; sn: SchemaNode): TypeSpec =
case sn.kind
of snkAtom:
case sn.atom
of akBool: (ident"bool", false)
of akFloat: (ident"float32", false)
of akDouble: (ident"float64", false)
of akInt: (ident"BiggestInt", false)
of akString: (ident"string", false)
of akBytes: (nn(nkBracketExpr, ident"seq", ident"byte"), false)
of akSymbol: (ident"string", false) # TODO: distinct string type for symbols?
of snkNamed:
typeIdent(scm, sn.pattern)
of snkRef:
var id = ident sn.refPath[sn.refPath.high]
for i in countDown(sn.refPath.high.pred, 0):
id = nn(nkDotExpr, ident(sn.refPath[i]), id)
(id, isEmbeddable(scm, sn))
else:
(preserveIdent(), true)
proc toExport(n: sink PNode): PNode =
nkPostFix.newNode.add(ident"*", n)
proc newEmpty(): PNode = newNode(nkEmpty)
proc literal(scm: Schema; sn: SchemaNode): Value =
case sn.kind
of snkLiteral: result = sn.value
of snkRef:
if sn.refPath.len == 1:
result = literal(scm, scm.definitions[sn.refPath[0]])
else:
raiseAssert("not convertable to a literal: " & $sn)
else:
raiseAssert("not convertable to a literal: " & $sn)
proc toEnumTy(sn: SchemaNode): PNode =
result = nkEnumTy.newNode.add newEmpty()
for bn in sn.nodes:
result.add bn.altLabel.nimIdentNormalize.ident.accQuote
proc toEnumDef(name: string; sn: SchemaNode): PNode =
nkTypeDef.newNode.add(
nkPragmaExpr.newNode.add(
name.ident.toExport,
nkPragma.newNode.add(ident"pure")),
newEmpty(),
sn.toEnumTy)
proc embeddingParams(embeddable: bool): PNode =
if embeddable:
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void"))
else:
newEmpty()
proc identDef(sn: SchemaNode; a, b: PNode; embeddable: bool): PNode =
if embeddable and b.kind != nkBracketExpr:
nn(nkIdentDefs, a, nn(nkBracketExpr, b, ident"E"), newEmpty())
else:
nn(nkIdentDefs, a, b, newEmpty())
proc identDef(sn: SchemaNode; l: PNode; ts: TypeSpec): PNode =
identDef(sn, l, ts.node, ts.embeddable)
proc typeDef(sn: SchemaNode; name: string; ty: PNode; embeddable: bool): PNode =
case sn.kind
of snkRecord:
nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.toExport,
nn(nkPragma,
nn(nkExprColonExpr,
ident"record",
PNode(kind: nkStrLit, strVal: sn.nodes[0].value.symbol)))),
embeddingParams(embeddable),
ty)
else:
nn(nkTypeDef, name.ident.toExport, embeddingParams(embeddable), ty)
proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""):
TypeSpec =
if name in known: return known[name]
case sn.kind
of snkOr:
if isSymbolEnum(scm, sn):
result.node = sn.toEnumTy
else:
let
enumName = name.nimIdentNormalize & "Kind"
enumIdent = ident(enumName)
if enumName notin known:
known[enumName] = (toEnumDef(enumName, sn), false)
let recCase = nkRecCase.newNode.add(
nkIdentDefs.newNode.add(
"kind".ident.toExport,
enumName.ident,
newEmpty()))
for bn in sn.nodes:
assert(bn.kind == snkAlt, $bn.kind)
doAssert(name != "", " no name for " & $sn)
var memberType: TypeSpec
if bn.altbranch.kind == snkRef:
memberType = typeIdent(scm, bn.altBranch)
else:
let memberTypeName = name & bn.altLabel.nimIdentNormalize
memberType.node = ident memberTypeName
if memberTypeName notin known:
let ty = nimTypeOf(scm, known, bn.altBranch, memberTypeName)
orEmbed memberType, ty
orEmbed result, memberType
known[memberTypeName] = (
typeDef(bn.altBranch, memberTypeName, ty.node, ty.embeddable),
ty.embeddable)
var recList = nkRecList.newNode
case bn.altBranch.kind
of snkRecord:
case bn.altBranch.nodes.len
of 0, 1: discard
of 2:
if not isConst(scm, bn.altBranch.nodes[1]):
let label = bn.ident
let branch = typeIdent(scm, bn.altBranch.nodes[1])
orEmbed result, branch
recList.add identDef(bn, label.toExport, branch)
else:
recList.add identDef(bn, bn.ident.toExport, memberType)
else:
if isConst(scm, bn.altBranch):
recList.add nkDiscardStmt.newNode.add(newEmpty())
else:
let label = bn.ident
let branch = typeIdent(scm, bn.altBranch)
orEmbed result, branch
recList.add identDef(bn, label.toExport, branch)
let disc = nkDotExpr.newNode.add(
enumIdent, bn.altLabel.nimIdentNormalize.ident.accQuote)
if recList.len == 0:
recList.add identDef(bn, bn.ident.toExport, memberType)
recCase.add nkOfBranch.newNode.add(disc, recList)
result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(),
newEmpty(),
nn(nkRecList, recCase)))
of snkAny:
result = (ident"Preserve", true)
of snkAtom:
result = typeIdent(scm, sn)
of snkEmbedded:
result = nimTypeOf(scm, known, sn.embed)
of snkLiteral:
result.node = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim
of pkBoolean: ident"bool"
of pkFloat: ident"float32"
of pkDouble: ident"float64"
of pkSignedInteger: ident"BiggestInt"
of pkBigInteger: ident"BigInt"
of pkString: ident"string"
of pkByteString: nn(
nkBracketExpr, ident"seq", ident"byte")
of pkSymbol: ident"string"
of pkRecord: preserveIdent()
of pkSequence: nn(
nkBracketExpr, ident"seq", preserveIdent())
of pkSet: nn(
nkBracketExpr, ident"HashSet", preserveIdent())
of pkDictionary: nn(
nkBracketExpr, ident"TableRef", preserveIdent(), preserveIdent())
of pkEmbedded:
raiseAssert "this should never happen"
of snkSequenceOf:
result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"seq",
parameterize(result))
of snkSetOf:
result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"HashedSet",
parameterize(result))
of snkDictOf:
let
key = nimTypeOf(scm, known, sn.nodes[0])
val = nimTypeOf(scm, known, sn.nodes[1])
orEmbed result, key
orEmbed result, val
result.node = nkBracketExpr.newNode.add(
ident"TableRef", parameterize(key), parameterize(val))
of snkRecord:
case sn.nodes.len
of 0, 1:
result.node = nn(nkObjectTy,
newEmpty(),
newEmpty(),
nn(nkRecList, nn(nkDiscardStmt, newEmpty())))
else:
let recList = nkRecList.newNode()
for i, field in sn.nodes:
if i > 0:
let
id = field.ident
fieldType = nimTypeOf(scm, known, field, $id)
orEmbed result, fieldType
recList.add identDef(sn, id.toExport, fieldType)
result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(),
newEmpty(),
recList))
of snkTuple:
result.node = nkTupleTy.newNode
for tn in sn.nodes:
if not isConst(scm, sn):
let fieldType = nimTypeOf(scm, known, tn)
orEmbed result, fieldType
result.node.add identDef(sn, tn.ident, fieldType)
of snkVariableTuple:
result.node = nkTupleTy.newNode
for i, tn in sn.nodes:
if not isConst(scm, sn):
let fieldType = nimTypeOf(scm, known, tn)
orEmbed result, fieldType
if i == sn.nodes.high:
result.node.add identDef(
tn,
tn.ident,
nn(nkBracketExpr, ident"seq", fieldType.node),
fieldType.embeddable)
else:
result.node.add identDef(tn, tn.ident, fieldType)
of snkDictionary:
result.node = nkTupleTy.newNode
for i in countup(0, sn.nodes.high, 2):
let id = ident(sn.nodes[i+0])
let fieldType = nimTypeOf(scm, known, sn.nodes[i+1], $id)
orEmbed result, fieldType
result.node.add identDef(sn.nodes[i+1], id, fieldType)
of snkNamed:
result = nimTypeOf(scm, known, sn.pattern, name)
of snkRef:
result = typeIdent(scm, sn)
else:
result.node = nkCommentStmt.newNode
result.node.comment = result.node.comment &
"Missing type generator for " & $sn.kind & " " & $sn
proc exportIdent(id: string): PNode = nn(nkPostFix, ident"*", ident(id))
proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: SchemaNode) =
case def.kind
of snkLiteral:
var stmts = nn(nkStmtList)
case def.value.kind
of pkSignedInteger:
discard stmts.add newIntNode(nkIntLit, def.value.int)
of pkSymbol:
discard stmts.add nn(nkCall,
nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: def.value.symbol))
else:
raiseAssert("conversion of " & $def & " to a Nim literal is not implemented")
var procId = name
procId[0] = procId[0].toLowerAscii
let constProc= nn(nkProcDef,
exportIdent(procId),
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
nn(nkFormalParams, preserveIdent()),
newEmpty(),
newEmpty(),
stmts)
constProc.comment = "``" & $def & "``"
result.add constProc
else: discard
proc nimLit(scm: Schema; sn: SchemaNode): PNode =
assert(sn.kind == snkLiteral, $sn)
case sn.value.kind
of pkSymbol:
nn(nkCall,
nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: sn.value.symbol))
else:
raiseAssert("no Nim literal for " & $sn)
proc literalToPreserveCall(pr: Preserve): PNode =
var prConstr = nn(nkObjConstr, preserveIdent())
proc constr(kind, field: string; lit: PNode) =
prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind))
prConstr.add nn(nkExprColonExpr, ident(field), lit)
case pr.kind
of pkBoolean:
constr($pr.kind, "bool", if pr.bool: ident"true" else: ident"false")
of pkFloat:
constr($pr.kind, "float", newFloatNode(nkFloat32Lit, pr.float))
of pkDouble:
constr($pr.kind, "double", newFloatNode(nkFloat64Lit, pr.double))
of pkSignedInteger:
constr($pr.kind, "int", newIntNode(nkInt64Lit, pr.int))
of pkString:
constr($pr.kind, "string", newStrNode(nkTripleStrLit, pr.string))
of pkByteString:
return nn(nkCall, ident"parsePreserves", newStrNode(nkTripleStrLit, $pr))
of pkSymbol:
constr($pr.kind, "symbol", newStrNode(nkStrLit, pr.symbol))
else:
raise newException(ValueError, "refusing to convert to a literal: " & $pr)
prConstr
proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
let seqBracket = nn(nkBracket)
for i, field in sn.nodes:
if isConst(scm, field):
seqBracket.add literalToPreserveCall(literal(scm, field))
elif sn.kind == snkTuple or i < sn.nodes.high:
seqBracket.add nn(nkCall,
ident"toPreserve", nn(nkDotExpr, ident, field.ident), ident"E")
let seqConstr = nn(nkPrefix, ident"@", seqBracket)
let colonExpr = nn(nkExprColonExpr, ident"sequence")
if sn.kind == snkTuple:
colonExpr.add seqConstr
else:
colonExpr.add nn(nkInfix,
ident"&",
seqConstr,
nn(nkDotExpr,
nn(nkCall, ident"toPreserve",
nn(nkDotExpr,
ident, sn.nodes[sn.nodes.high].ident),
ident"E"),
ident"sequence"))
nn(nkObjConstr,
preserveIdent(),
nn(nkExprColonExpr, ident"kind", ident"pkSequence"),
colonExpr)
proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: SchemaNode) =
case sn.kind
of snkOr:
var
enumId = name.ident
paramId = ident"v"
orStmts = nn(nkStmtList)
if isSymbolEnum(scm, sn):
let caseStmt = nn(nkCaseStmt, paramId)
for bn in sn.nodes:
caseStmt.add nn(nkOfBranch,
nn(nkDotExpr,
enumId,
bn.altLabel.nimIdentNormalize.ident.accQuote),
nn(nkCall,
nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: $bn.altLabel)))
orStmts.add caseStmt
else:
let caseStmt = nn(nkCaseStmt, nn(nkDotExpr, paramId, ident"kind"))
proc genStmts(stmts: PNode; fieldId: PNode; sn: SchemaNode) =
case sn.kind
of snkLiteral:
stmts.add literalToPreserveCall(literal(scm, sn))
of snkOr, snkRecord, snkRef:
if sn.kind == snkRef and sn.refPath.len == 1:
let refDef = scm.definitions[sn.refPath[0]]
genStmts(stmts, fieldId, refDef)
else:
stmts.add nn(nkCall,
ident"toPreserve",
nn(nkDotExpr, paramId, fieldId), ident"E")
of snkTuple, snkVariableTuple:
stmts.add tupleConstructor(scm, sn, nn(nkDotExpr, paramId, fieldId))
of snkAtom, snkSequenceOf:
stmts.add nn(nkCall,
ident"toPreserve",
nn(nkDotExpr, paramId, fieldId), ident"E")
else:
raiseAssert("no case statement for " & $sn.kind & " " & $sn)
for bn in sn.nodes:
let stmts = nn(nkStmtList)
genStmts(stmts, bn.ident, bn.altBranch)
caseStmt.add nn(nkOfBranch,
nn(nkDotExpr,
ident(name & "Kind"),
bn.altLabel.nimIdentNormalize.ident.accQuote),
stmts)
orStmts.add caseStmt
result.add nn(nkProcDef,
exportIdent("toPreserveHook"),
newEmpty(),
newEmpty(),
nn(nkFormalParams,
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
orStmts)
of snkRecord:
var
params = nn(nkFormalParams, preserveIdent())
initRecordCall = nn(nkCall,
nn(nkBracketExpr, ident"initRecord", ident"E"),
nimLit(scm, sn.nodes[0]))
for i, field in sn.nodes:
if i > 0:
let
id = field.ident
var (fieldType, embeddable) = typeIdent(scm, field)
if not fieldType.isPreserve:
fieldType =
nn(nkInfix,
ident"|",
fieldType,
preserveIdent())
params.add nn(nkIdentDefs,
id, fieldType, newEmpty())
initRecordCall.add(
nn(nkCall, ident"toPreserve", id, ident"E"))
var procId = name
procId[0] = procId[0].toLowerAscii
let cmt = nkCommentStmt.newNode
cmt.comment = "Preserves constructor for ``" & name & "``."
result.add nn(nkProcDef,
procId.ident.accQuote.toExport,
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
params,
newEmpty(),
newEmpty(),
nn(nkStmtList,
cmt, initRecordCall))
block:
let paramId = name.toLowerAscii.ident.accQuote
initRecordCall = nn(nkCall,
nn(nkBracketExpr, ident"initRecord", ident"E"),
nimLit(scm, sn.nodes[0]))
for i, field in sn.nodes:
if i > 0:
initRecordCall.add nn(nkCall,
ident"toPreserve",
nn(nkDotExpr, paramId, field.ident),
ident"E")
result.add nn(nkProcDef,
exportIdent("toPreserveHook"),
newEmpty(),
newEmpty(),
nn(nkFormalParams,
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList, initRecordCall))
of snkTuple, snkVariableTuple:
let paramId = name.toLowerAscii.ident.accQuote
result.add nn(nkProcDef,
exportIdent("toPreserveHook"),
newEmpty(),
newEmpty(),
nn(nkFormalParams,
preserveIdent(),
nn(nkIdentDefs,
paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList, tupleConstructor(scm, sn, paramId)))
else: discard
proc collectRefImports(imports: PNode; sn: SchemaNode) =
case sn.kind
of snkLiteral:
if sn.value.isDictionary:
imports.add ident"std/tables"
of snkDictOf:
imports.add ident"std/tables"
of snkRef:
if sn.refPath.len > 1:
imports.add ident(sn.refPath[0])
else:
for child in sn.items:
collectRefImports(imports, child)
proc collectRefImports(imports: PNode; scm: Schema) =
for _, def in scm.definitions:
collectRefImports(imports, def)
proc generateNimFile*(scm: Schema; path: string) =
var
knownTypes: TypeTable
typeSection = newNode nkTypeSection
procs: seq[PNode]
megaType: PNode
for name, def in scm.definitions.pairs:
if isConst(scm, def):
generateConstProcs(procs, scm, name, def)
else:
var name = name
name[0] = name[0].toUpperAscii
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
if megaType.isNil:
megaType = defIdent
else:
megaType = nn(nkInfix,
ident"|", megaType, defIdent)
let (t, embeddable) =
if def.kind == snkAny: (preserveIdent(), true)
else: nimTypeOf(scm, knownTypes, def, name)
t.comment = "``" & $def & "``"
case def.kind
of snkAtom:
knownTypes[name] = (nkTypeDef.newNode.add(
name.ident.toExport, embeddingParams(false), t),
embeddable)
else:
if def.kind == snkRecord:
knownTypes[name] = (typeDef(def, name, t, embeddable), embeddable)
else:
case t.kind
of nkEnumTy:
knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.toExport,
nn(nkPragma, ident"pure")),
newEmpty(),
t), false)
of nkDistinctTy:
knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.toExport,
nn(nkPragma,
nn(nkExprColonExpr,
ident"borrow",
accQuote(ident".")))),
newEmpty(),
t), embeddable)
else:
knownTypes[name] = (nn(nkTypeDef,
name.ident.toExport, embeddingParams(embeddable), t),
embeddable)
generateProcs(procs, scm, name, def)
for (typeDef, _) in knownTypes.values:
typeSection.add typeDef
var imports = nkImportStmt.newNode.add(
ident"std/typetraits",
ident"preserves")
collectRefImports(imports, scm)
procs.add nn(nkProcDef,
"$".ident.accQuote.toExport,
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams,
ident"string",
nn(nkIdentDefs,
ident"x",
megaType,
newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"$",
nn(nkCall, ident"toPreserve", ident"x", ident"E"))))
procs.add nn(nkProcDef,
"encode".ident.accQuote.toExport,
newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams,
nn(nkBracketExpr, ident"seq", ident"byte"),
nn(nkIdentDefs,
ident"x",
megaType,
newEmpty())),
newEmpty(),
newEmpty(),
nn(nkStmtList,
nn(nkCall, ident"encode", nn(nkCall,
ident"toPreserve", ident"x", ident"E"))))
var module = newNode(nkStmtList).add(
imports,
typeSection
).add(procs)
writeFile(path, renderTree(module, {renderNone, renderIr}))
when isMainModule:
import std/parseopt
var inputs: seq[string]
for kind, key, val in getopt():
case kind
of cmdLongOption:
case key
else: quit("unhandled option " & key)
of cmdShortOption:
case key
else: quit("unhandled option " & key)
of cmdArgument:
inputs.add absolutePath(key)
of cmdEnd: discard
for filepath in inputs:
let
scm = parsePreservesSchema(readFile filepath, filepath)
(dir, name, _) = splitFile(filepath)
outputPath = dir / name & ".nim"
generateNimFile(scm, outputPath)
stdout.writeLine(outputPath)

215
src/preserves/schema.nim Normal file
View File

@ -0,0 +1,215 @@
import
std/typetraits, preserves, std/tables
type
Ref* {.preservesRecord: "ref".} = object
`module`*: ModulePath
`name`* {.preservesSymbol.}: string
ModulePath* = seq[string]
Bundle* {.preservesRecord: "bundle".} = object
`modules`*: Modules
CompoundPatternKind* {.pure.} = enum
`rec`, `tuple`, `tuplePrefix`, `dict`
CompoundPatternRec* {.preservesRecord: "rec".} = object
`label`*: NamedPattern
`fields`*: NamedPattern
CompoundPatternTuple* {.preservesRecord: "tuple".} = object
`patterns`*: seq[NamedPattern]
CompoundPatternTuplePrefix* {.preservesRecord: "tuplePrefix".} = object
`fixed`*: seq[NamedPattern]
`variable`*: NamedSimplePattern
CompoundPatternDict* {.preservesRecord: "dict".} = object
`entries`*: DictionaryEntries
`CompoundPattern`* {.preservesOr.} = ref object
case orKind*: CompoundPatternKind
of CompoundPatternKind.`rec`:
`rec`*: CompoundPatternRec
of CompoundPatternKind.`tuple`:
`tuple`*: CompoundPatternTuple
of CompoundPatternKind.`tuplePrefix`:
`tupleprefix`*: CompoundPatternTuplePrefix
of CompoundPatternKind.`dict`:
`dict`*: CompoundPatternDict
Modules* = Table[ModulePath, Schema]
EmbeddedTypeNameKind* {.pure.} = enum
`Ref`, `false`
`EmbeddedTypeName`* {.preservesOr.} = ref object
case orKind*: EmbeddedTypeNameKind
of EmbeddedTypeNameKind.`Ref`:
`ref`*: Ref
of EmbeddedTypeNameKind.`false`:
`false`* {.preservesLiteral: "#f".}: bool
`AtomKind`* {.preservesOr.} = enum
`Boolean`, `Float`, `Double`, `SignedInteger`, `String`, `ByteString`,
`Symbol`
Definitions* = Table[string, Definition]
DictionaryEntries* = Table[Preserve[void], NamedSimplePattern]
NamedPatternKind* {.pure.} = enum
`named`, `anonymous`
`NamedPattern`* {.preservesOr.} = ref object
case orKind*: NamedPatternKind
of NamedPatternKind.`named`:
`named`*: Binding
of NamedPatternKind.`anonymous`:
`anonymous`*: Pattern
SimplePatternKind* {.pure.} = enum
`any`, `atom`, `embedded`, `lit`, `seqof`, `setof`, `dictof`, `Ref`
SimplePatternAtom* {.preservesRecord: "atom".} = object
`atomKind`*: AtomKind
SimplePatternEmbedded* {.preservesRecord: "embedded".} = object
`interface`*: SimplePattern
SimplePatternLit* {.preservesRecord: "lit".} = object
`value`*: Preserve[void]
SimplePatternSeqof* {.preservesRecord: "seqof".} = object
`pattern`*: SimplePattern
SimplePatternSetof* {.preservesRecord: "setof".} = object
`pattern`*: SimplePattern
SimplePatternDictof* {.preservesRecord: "dictof".} = object
`key`*: SimplePattern
`value`*: SimplePattern
`SimplePattern`* {.preservesOr.} = ref object
case orKind*: SimplePatternKind
of SimplePatternKind.`any`:
`any`* {.preservesLiteral: "any".}: bool
of SimplePatternKind.`atom`:
`atom`*: SimplePatternAtom
of SimplePatternKind.`embedded`:
`embedded`*: SimplePatternEmbedded
of SimplePatternKind.`lit`:
`lit`*: SimplePatternLit
of SimplePatternKind.`seqof`:
`seqof`*: SimplePatternSeqof
of SimplePatternKind.`setof`:
`setof`*: SimplePatternSetof
of SimplePatternKind.`dictof`:
`dictof`*: SimplePatternDictof
of SimplePatternKind.`Ref`:
`ref`*: Ref
NamedSimplePatternKind* {.pure.} = enum
`named`, `anonymous`
`NamedSimplePattern`* {.preservesOr.} = ref object
case orKind*: NamedSimplePatternKind
of NamedSimplePatternKind.`named`:
`named`*: Binding
of NamedSimplePatternKind.`anonymous`:
`anonymous`*: SimplePattern
DefinitionKind* {.pure.} = enum
`or`, `and`, `Pattern`
DefinitionOrData* {.preservesTuple.} = object
`pattern0`*: NamedAlternative
`pattern1`*: NamedAlternative
`patternN`* {.preservesTupleTail.}: seq[NamedAlternative]
DefinitionOr* {.preservesRecord: "or".} = object
`data`*: DefinitionOrData
DefinitionAndData* {.preservesTuple.} = object
`pattern0`*: NamedPattern
`pattern1`*: NamedPattern
`patternN`* {.preservesTupleTail.}: seq[NamedPattern]
DefinitionAnd* {.preservesRecord: "and".} = object
`data`*: DefinitionAndData
`Definition`* {.preservesOr.} = ref object
case orKind*: DefinitionKind
of DefinitionKind.`or`:
`or`*: DefinitionOr
of DefinitionKind.`and`:
`and`*: DefinitionAnd
of DefinitionKind.`Pattern`:
`pattern`*: Pattern
NamedAlternative* {.preservesTuple.} = object
`variantLabel`*: string
`pattern`*: Pattern
SchemaData* {.preservesDictionary.} = object
`embeddedType`*: EmbeddedTypeName
`version`* {.preservesLiteral: "1".}: bool
`definitions`*: Definitions
Schema* {.preservesRecord: "schema".} = object
`data`*: SchemaData
PatternKind* {.pure.} = enum
`SimplePattern`, `CompoundPattern`
`Pattern`* {.preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`SimplePattern`:
`simplepattern`*: SimplePattern
of PatternKind.`CompoundPattern`:
`compoundpattern`*: CompoundPattern
Binding* {.preservesRecord: "named".} = object
`name`* {.preservesSymbol.}: string
`pattern`*: SimplePattern
proc `$`*(x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
EmbeddedTypeName |
Definitions |
DictionaryEntries |
NamedPattern |
SimplePattern |
NamedSimplePattern |
Definition |
NamedAlternative |
Schema |
Pattern |
Binding): string =
`$`(toPreserve(x))
proc encode*[E](x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
EmbeddedTypeName |
Definitions |
DictionaryEntries |
NamedPattern |
SimplePattern |
NamedSimplePattern |
Definition |
NamedAlternative |
Schema |
Pattern |
Binding): seq[byte] =
encode(toPreserve(x, E))

View File

@ -1,450 +0,0 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
## NPeg parser for Preserves Schemas.
## https://preserves.gitlab.io/preserves/preserves-schema.html
import std/[parseutils, macros, strutils, tables]
from os import absolutePath, isAbsolute, parentDir
import npeg
import ../preserves, ./parse, ./pegs
type
Value = Preserve[void]
Stack = seq[tuple[node: SchemaNode, pos: int]]
SchemaNodeKind* = enum
# TODO: audit these kinds later
snkOr,
snkAnd,
snkAlt, # same as snkNamed?
snkAny,
snkAtom,
snkEmbedded,
snkLiteral,
snkSequenceOf
snkSetOf,
snkDictOf,
snkRecord,
snkTuple,
snkVariableTuple,
snkDictionary,
snkNamed,
snkRef
AtomKind* = enum
akBool = "bool"
akFloat = "float"
akDouble = "double"
akInt = "int"
akString = "string"
akBytes = "bytes"
akSymbol = "symbol"
SchemaNode* {.acyclic.} = ref object
case kind*: SchemaNodeKind
of snkAlt:
altLabel*: string
altBranch*: SchemaNode
of snkAny: discard
of snkAtom:
atom*: AtomKind
of snkEmbedded:
embed*: SchemaNode
of snkLiteral:
value*: Value
of snkNamed:
name*: string
pattern*: SchemaNode
of snkRef:
refPath*: seq[string]
else:
nodes*: seq[SchemaNode]
Schema* = ref object
version*: int
embeddedType*: string
definitions*: OrderedTable[string, SchemaNode]
ParseState = object
filepath: string
stack: Stack
schema: Schema
proc add(a: SchemaNode; b: SchemaNode|seq[SchemaNode]): SchemaNode {.discardable.} =
a.nodes.add b
a
iterator items*(sn: SchemaNode): SchemaNode =
case sn.kind
of snkAny, snkAtom, snkLiteral, snkRef: discard
of snkAlt:
yield sn.altBranch
of snkEmbedded:
yield sn.embed
of snkNamed:
yield sn.pattern
else:
for i in 0..sn.nodes.high:
yield sn.nodes[i]
proc `$`*(n: SchemaNode): string =
case n.kind
of snkOr:
result.add "/ "
result.add join(n.nodes, " / ")
of snkAnd:
result.add "& "
result.add join(n.nodes, " & ")
of snkAlt:
case n.altBranch.kind
of snkRecord, snkRef:
result.add $n.altBranch
of snkLiteral:
result.add '='
result.add $n.altBranch
else:
result.add '@'
result.add n.altLabel
result.add ' '
result.add $n.altBranch
of snkAny: result.add "any"
of snkAtom: result.add $n.atom
of snkEmbedded:
result.add "#!" & $n.embed
of snkLiteral:
case n.value.kind
of pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString:
result.add $n.value
else:
result.add "<<lit>" & $n.value & ">"
of snkSequenceOf:
result.add "[ "
result.add $n.nodes[0]
result.add " ... ]"
of snkSetOf:
result.add "#{"
result.add n.nodes.join(" ")
result.add '}'
of snkDictOf:
result.add '{'
result.add $n.nodes[0]
result.add " : "
result.add $n.nodes[1]
result.add " ...:...}"
of snkRecord:
result.add '<'
if n.nodes[0].kind == snkLiteral and n.nodes[0].value.kind == pkSymbol:
result.add n.nodes[0].value.symbol
for i in 1..n.nodes.high:
result.add ' '
result.add $n.nodes[i]
else:
result.add join(n.nodes, " ")
result.add '>'
of snkTuple:
result.add '['
result.add join(n.nodes, " ")
result.add ']'
of snkVariableTuple:
result.add '['
result.add join(n.nodes, " ")
result.add " ...]"
of snkDictionary:
result.add '{'
for i in countup(0, n.nodes.high, 2):
result.add $n.nodes[i]
result.add ": "
result.add $n.nodes[i.succ]
result.add ' '
result.add '}'
of snkNamed:
result.add '@'
result.add n.name
result.add ' '
result.add $n.pattern
of snkRef:
result.add join(n.refPath, ".")
proc `$`*(scm: Schema): string =
result.add("version = $1 .\n" % $scm.version)
if scm.embeddedType != "":
result.add("EmbeddedTypeName = $1 .\n" % scm.embeddedType)
for n, d in scm.definitions.pairs:
result.add("$1 = $2 .\n" % [n, $d])
proc `$`(stack: Stack): string =
for f in stack:
result.add "\n"
result.add $f.pos
result.add ": "
result.add $f.node
proc match(text: string; p: var ParseState) {.gcsafe.}
template newSchemaNode(snk: SchemaNodeKind): SchemaNode =
SchemaNode(kind: snk)
template takeStackAt(): seq[SchemaNode] =
var nodes = newSeq[SchemaNode]()
let pos = capture[0].si
var i: int
while i < p.stack.len and p.stack[i].pos < pos:
inc i
let stop = i
while i < p.stack.len:
nodes.add(move p.stack[i].node)
inc i
p.stack.setLen(stop)
nodes
template takeStackAfter(): seq[SchemaNode] =
var nodes = newSeq[SchemaNode]()
let pos = capture[0].si
var i: int
while i < p.stack.len and p.stack[i].pos <= pos:
inc i
let stop = i
while i < p.stack.len:
nodes.add(move p.stack[i].node)
inc i
p.stack.setLen(stop)
nodes
template popStack(): SchemaNode =
assert(p.stack.len > 0, capture[0].s)
assert(capture[0].si <= p.stack[p.stack.high].pos, capture[0].s)
p.stack.pop.node
template pushStack(n: SchemaNode) =
let pos = capture[0].si
var i: int
while i < p.stack.len and p.stack[i].pos < pos:
inc i
p.stack.setLen(i)
p.stack.add((n, pos))
assert(p.stack.len > 0, capture[0].s)
const parser = peg("Schema", p: ParseState):
Schema <- ?editorCruft * S * +(Clause * S) * !1
Clause <- (Version | EmbeddedTypeName | Include | Definition) * S * '.'
Version <- "version" * S * >(*Digit):
discard parseInt($1, p.schema.version)
if p.schema.version != 1: fail()
EmbeddedTypeName <- "embeddedType" * S * >("#f" | Ref):
if p.schema.embeddedType != "": fail()
if $1 != "#f": p.schema.embeddedType = $1
Include <- "include" * S * (>(+Alnum) | ('"' * >(@'"'))):
var ip = ParseState(
schema: p.schema,
filepath:
if isAbsolute($1): $1
else: absolutePath($1, p.filepath.parentDir))
ip.filePath.setLen(ip.filePath.high)
match(readFile ip.filepath, ip)
Definition <- >id * S * '=' * S * (OrPattern | AndPattern | Pattern):
if p.schema.definitions.hasKey $1:
raise newException(ValueError, "duplicate definition of " & $1)
p.schema.definitions[$1] = popStack()
p.stack.setLen(0)
OrPattern <- ?('/' * S) * AltPattern * +(S * '/' * S * AltPattern):
let n = snkOr.newSchemaNode.add(takeStackAt())
assert(n.nodes[0].kind == snkAlt, $n.nodes[0])
pushStack n
AltPattern <-
AltNamed |
AltRecord |
AltRef |
AltLiteralPattern
AltNamed <- '@' * >id * S * Pattern:
let n = SchemaNode(kind: snkAlt, altLabel: $1, altBranch: popStack())
pushStack n
AltRecord <- '<' * >id * *(S * NamedPattern) * '>':
let
id = SchemaNode(kind: snkLiteral, value: symbol[void]($1))
n = SchemaNode(kind: snkAlt,
altLabel: $1,
altBranch: snkRecord.newSchemaNode.add(id).add(takeStackAt()))
pushStack n
AltRef <- Ref:
let n = SchemaNode(kind: snkAlt, altLabel: $0, altBranch: popStack())
pushStack n
AltLiteralPattern <-
>Preserves.Boolean |
>Preserves.Float |
>Preserves.Double |
>Preserves.SignedInteger |
>Preserves.String |
'=' * >Preserves.Symbol:
let
branch = SchemaNode(kind: snkLiteral, value: parsePreserves($1))
label = case branch.value.kind
of pkBoolean:
if branch.value.bool: "true" else: "false"
else: $branch.value
# TODO: numbers?
pushStack SchemaNode(kind: snkAlt, altLabel: label, altBranch: branch)
AndPattern <- ?('&' * S) * NamedPattern * +(S * '&' * S * NamedPattern)
Pattern <- SimplePattern | CompoundPattern
SimplePattern <-
AnyPattern |
AtomKindPattern |
EmbeddedPattern |
LiteralPattern |
SequenceOfPattern |
SetOfPattern |
DictOfPattern |
Ref
AnyPattern <- "any":
let n = SchemaNode(kind: snkAny)
pushStack n
AtomKindPattern <-
"bool" |
"float" |
"double" |
"int" |
"string" |
"bytes" |
"symbol":
let n = SchemaNode(kind: snkAtom)
case $0
of "bool": n.atom = akBool
of "float": n.atom = akFloat
of "double": n.atom = akDouble
of "int": n.atom = akInt
of "string": n.atom = akString
of "bytes": n.atom = akBytes
of "symbol": n.atom = akSymbol
pushStack n
EmbeddedPattern <- "#!" * SimplePattern:
let n = SchemaNode(kind: snkEmbedded, embed: popStack())
pushStack n
LiteralPattern <- ('=' * >symbol) | ("<<lit>" * >Preserves.Value * ">") | >nonSymbolAtom:
let n = SchemaNode(
kind: snkLiteral,
value: parsePreserves($1))
pushStack n
SequenceOfPattern <- '[' * S * SimplePattern * S * "..." * S * ']':
let n = newSchemaNode(snkSequenceOf).add(takeStackAfter())
pushStack n
SetOfPattern <- "#{" * SimplePattern * '}'
DictOfPattern <-
'{' *
S * SimplePattern * S * ':' * S * SimplePattern * S * "...:..." * S *
'}':
let n = newSchemaNode(snkDictOf).add(takeStackAfter())
assert(n.nodes.len == 2, $n.nodes)
pushStack n
Ref <- >(Alpha * *Alnum) * *('.' * >(*Alnum)):
let n = SchemaNode(kind: snkRef)
for i in 1..<capture.len: n.refPath.add capture[i].s
pushStack n
CompoundPattern <-
RecordPattern |
TuplePattern |
VariableTuplePattern |
DictionaryPattern
RecordPattern <-
("<<rec>" * S * NamedPattern * *(S * NamedPattern) * '>') |
('<' * >Value * *(S * NamedPattern) * '>'):
let n = newSchemaNode(snkRecord).add(takeStackAfter())
pushStack n
TuplePattern <-
'[' * S * *(NamedPattern * S) * ']':
var n = SchemaNode(kind: snkTuple)
for frame in p.stack.mitems:
if frame.pos > capture[0].si:
n.nodes.add(move frame.node)
pushStack n
VariableTuplePattern <-
'[' * S * *(NamedPattern * S) * ?(Pattern * S) * "..." * S * ']':
var n = SchemaNode(kind: snkVariableTuple)
for frame in p.stack.mitems:
if frame.pos > capture[0].si:
n.nodes.add(move frame.node)
pushStack n
DictionaryPattern <- '{' * S * *(Value * S * ':' * S * NamedSimplePattern * S) * '}':
var n = SchemaNode(kind: snkDictionary)
for frame in p.stack.mitems:
if frame.pos > capture[0].si:
n.nodes.add(move frame.node)
pushStack n
NamedPattern <- ('@' * >id * S * SimplePattern) | Pattern:
if capture.len == 2:
var n = SchemaNode(
kind: snkNamed, name: $1, pattern: popStack())
pushStack n
NamedSimplePattern <- ('@' * >id * S * SimplePattern) | SimplePattern:
if capture.len == 2:
var n = SchemaNode(
kind: snkNamed, name: $1, pattern: popStack())
pushStack n
id <- Alpha * *Alnum
Comment <- ';' * @'\n'
S <- *(Space | Comment)
symbol <- Preserves.Symbol
nonSymbolAtom <-
Preserves.Boolean |
Preserves.Float |
Preserves.Double |
Preserves.SignedInteger |
Preserves.String |
Preserves.ByteString
Value <- Preserves.Value:
pushStack SchemaNode(
kind: snkLiteral,
value: parsePreserves($0))
editorCruft <- '@' * @'\n'
proc match(text: string; p: var ParseState) =
let match = parser.match(text, p)
if not match.ok:
raise newException(ValueError, "failed to parse " & p.filepath & ":\n" & text[0..<match.matchMax])
proc parsePreservesSchema*(text, filepath: string): Schema =
## Parse a Preserves schema into an abstract syntax tree represented as a `Preserve`.
var p = ParseState(filepath: filepath)
new p.schema
match(text, p)
result = p.schema
if result.version != 1:
raise newException(ValueError, "missing or invalid Preserves schema version")

21
tests/test_schemas.nim Normal file
View File

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[tables, options, os, unittest]
import preserves, preserves/parse, preserves/schema
suite "schema":
const
binPath = "upstream/schema/schema.bin"
test "convertability":
if not fileExists(binPath): skip()
else:
var
b = decodePreserves readFile(binPath)
scm = preserveTo(b, Schema)
check scm.isSome
if scm.isSome:
var a = toPreserve(get scm)
check(a == b)

1
upstream Submodule

@ -0,0 +1 @@
Subproject commit b2c3032e7a9c5157aaea88a77be83438b7a23c58