diff --git a/.gitignore b/.gitignore index 5fbb3c2..38bfa7a 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d556bfa --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "upstream"] + path = upstream + url = https://gitlab.com/preserves/preserves.git diff --git a/preserves.nimble b/preserves.nimble index 02ae47c..a4c8e33 100644 --- a/preserves.nimble +++ b/preserves.nimble @@ -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 diff --git a/src/preserves/preserves_schema_nim.nim b/src/preserves/preserves_schema_nim.nim new file mode 100644 index 0000000..f6ccf25 --- /dev/null +++ b/src/preserves/preserves_schema_nim.nim @@ -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 diff --git a/src/preserves/private/preserves_schema_nim.nim b/src/preserves/private/preserves_schema_nim.nim deleted file mode 100644 index 86e7c5b..0000000 --- a/src/preserves/private/preserves_schema_nim.nim +++ /dev/null @@ -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) diff --git a/src/preserves/schema.nim b/src/preserves/schema.nim new file mode 100644 index 0000000..3459bb7 --- /dev/null +++ b/src/preserves/schema.nim @@ -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)) diff --git a/src/preserves/schemas.nim b/src/preserves/schemas.nim deleted file mode 100644 index 9a61c63..0000000 --- a/src/preserves/schemas.nim +++ /dev/null @@ -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 "<" & $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) | ("<" * >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.." * 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..