# SPDX-FileCopyrightText: ☭ 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 --path:../../../nim --path:.. -r ./preserves_schema_nim ../../../preserves/schema/schema.bin import std/[hashes, sets, strutils, tables] # Cannot use std/macros, must use compiler modules because # we are generating code at run-time. import compiler/[ast, idents, renderer, lineinfos] import ../preserves, ./schema type Attribute = enum embedded ## type contains an embedded value Attributes = set[Attribute] TypeSpec = object node: PNode attrs: Attributes TypeTable = OrderedTable[schema.ModulePath, PNode] Location = tuple[bundle: Bundle, schemaPath: ModulePath] StringSet = HashSet[string] proc schema(loc: Location): Schema = loc.bundle.modules[loc.schemaPath] 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 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(string np.named.name) of NamedPatternKind.anonymous: ident(fallback) proc ident(np: NamedSimplePattern; fallback: string): PNode = case np.orKind of NamedSimplePatternKind.named: ident(string np.named.name) of NamedSimplePatternKind.anonymous: ident(fallback) proc isEmbedded(ts: TypeSpec): bool = embedded in ts.attrs func isAtomic(r: Ref): bool = case r.name.string of "bool", "float", "double", "int", "string", "bytes", "symbol": true else: false proc addAttrs(x: var TypeSpec; y: TypeSpec) = x.attrs = x.attrs + y.attrs proc dotExtend(result: var PNode; label: string) = var id = ident(label) if result.isNil: result = id else: result = nkDotExpr.newTree(result, id) proc ident(`ref`: Ref): PNode = for m in `ref`.module: dotExtend(result, string m) if `ref`.isAtomic: dotExtend(result, `ref`.name.string) else: dotExtend(result, `ref`.name.string.capitalizeAscii) proc deref(loc: Location; r: Ref): (Location, Definition) = try: result[0] = loc if r.module == @[]: result[1] = loc.bundle.modules[loc.schemaPath].field0.definitions[r.name] else: result[0].schemaPath = r.module result[1] = loc.bundle.modules[r.module].field0.definitions[r.name] except KeyError: raise newException(KeyError, "reference not found in bundle: " & $r) proc hasEmbeddedType(scm: Schema): bool = case scm.field0.embeddedType.orKind of EmbeddedtypenameKind.false: false of EmbeddedtypenameKind.Ref: true proc parameterize(loc: Location; node: PNode; embeddable: bool): PNode = node proc parameterize(loc: Location; spec: TypeSpec): PNode = parameterize(loc, spec.node, spec.isEmbedded) proc hash(r: Ref): Hash = r.toPreserves.hash type RefSet = HashSet[Ref] proc attrs(loc: Location; pat: Pattern; seen: RefSet): Attributes {.gcsafe.} proc attrs(loc: Location; def: Definition; seen: RefSet): Attributes {.gcsafe.} proc attrs(loc: Location; n: NamedAlternative|NamedPattern; seen: RefSet): Attributes = attrs(loc, n.pattern, seen) proc attrs(loc: Location; sp: SimplePattern; seen: RefSet): Attributes = case sp.orKind of SimplepatternKind.atom, SimplepatternKind.lit, SimplepatternKind.any: {} of SimplepatternKind.embedded: {embedded} of SimplepatternKind.seqof: attrs(loc, sp.seqof.pattern, seen) of SimplepatternKind.setof: attrs(loc, sp.setof.pattern, seen) of SimplepatternKind.dictof: attrs(loc, sp.dictof.key, seen) + attrs(loc, sp.dictof.value, seen) of SimplepatternKind.Ref: if (sp.ref in seen) or sp.ref.isAtomic: {} else: var (loc, def) = deref(loc, sp.ref) seen = seen incl(seen, sp.ref) attrs(loc, def, seen) proc attrs(loc: Location; np: NamedSimplePattern; seen: RefSet): Attributes = case np.orKind of NamedSimplePatternKind.named: attrs(loc, np.named.pattern, seen) of NamedSimplePatternKind.anonymous: attrs(loc, np.anonymous, seen) proc attrs(loc: Location; cp: CompoundPattern; seen: RefSet): Attributes = case cp.orKind of CompoundPatternKind.rec: result = attrs(loc, cp.rec.label.pattern, seen) + attrs(loc, cp.rec.fields.pattern, seen) of CompoundPatternKind.tuple: for np in cp.tuple.patterns: result = result + attrs(loc, np.pattern, seen) of CompoundPatternKind.tupleprefix: result = attrs(loc, cp.tupleprefix.variable, seen) for p in cp.tupleprefix.fixed: result = result + attrs(loc, p, seen) of CompoundPatternKind.dict: for nsp in cp.dict.entries.values: result = result + attrs(loc, nsp, seen) proc attrs(loc: Location; pat: Pattern; seen: RefSet): Attributes = case pat.orKind of PatternKind.SimplePattern: attrs(loc, pat.simplePattern, seen) of PatternKind.CompoundPattern: attrs(loc, pat.compoundPattern, seen) proc attrs(loc: Location; def: DefinitionOr|DefinitionAnd; seen: RefSet): Attributes = result = attrs(loc, def.field0.pattern0, seen) + attrs(loc, def.field0.pattern1, seen) for p in def.field0.patternN: result = result + attrs(loc, p, seen) proc attrs(loc: Location; def: Definition; seen: RefSet): Attributes = case def.orKind of DefinitionKind.or: result = attrs(loc, def.or, seen) of DefinitionKind.and: result = attrs(loc, def.and, seen) of DefinitionKind.Pattern: result = attrs(loc, def.pattern, seen) proc attrs(loc: Location; p: Definition|DefinitionOr|DefinitionAnd|Pattern|CompoundPattern|SimplePattern): Attributes = var seen: RefSet attrs(loc, p, seen) proc isEmbedded(loc: Location; p: Definition|DefinitionOr|DefinitionAnd|Pattern|CompoundPattern|SimplePattern): bool = embedded in attrs(loc, p) proc isRecursive(loc: Location; name: string; pat: Pattern; seen: RefSet): bool {.gcsafe.} proc isRecursive(loc: Location; name: string; def: Definition; seen: RefSet): bool {.gcsafe.} proc isRecursive(loc: Location; name: string; n: NamedAlternative|NamedPattern; seen: RefSet): bool = isRecursive(loc, name, n.pattern, seen) proc isRecursive(loc: Location; name: string; sp: SimplePattern; seen: RefSet): bool = case sp.orKind of SimplepatternKind.embedded: isRecursive(loc, name, sp.embedded.interface, seen) of SimplepatternKind.Ref: if sp.ref.name.string == name: true elif sp.ref in seen: false else: var (loc, def) = deref(loc, sp.ref) seen = seen incl(seen, sp.ref) isRecursive(loc, name, def, seen) else: false # seqof, setof, and dictof are not processed # because they imply pointer indirection proc isRecursive(loc: Location; name: string; np: NamedSimplePattern; seen: RefSet): bool = case np.orKind of NamedSimplePatternKind.named: isRecursive(loc, name, np.named.pattern, seen) of NamedSimplePatternKind.anonymous: isRecursive(loc, name, np.anonymous, seen) proc isRecursive(loc: Location; name: string; cp: CompoundPattern; seen: RefSet): bool = case cp.orKind of CompoundPatternKind.rec: result = isRecursive(loc, name, cp.rec.label.pattern, seen) or isRecursive(loc, name, cp.rec.fields.pattern, seen) of CompoundPatternKind.tuple: for np in cp.tuple.patterns: if result: return result = isRecursive(loc, name, np.pattern, seen) of CompoundPatternKind.tupleprefix: result = isRecursive(loc, name, cp.tupleprefix.variable, seen) for p in cp.tupleprefix.fixed: if result: return result = isRecursive(loc, name, p, seen) of CompoundPatternKind.dict: for nsp in cp.dict.entries.values: if result: return result = isRecursive(loc, name, nsp, seen) proc isRecursive(loc: Location; name: string; pat: Pattern; seen: RefSet): bool = case pat.orKind of PatternKind.SimplePattern: isRecursive(loc, name, pat.simplePattern, seen) of PatternKind.CompoundPattern: isRecursive(loc, name, pat.compoundPattern, seen) proc isRecursive(loc: Location; name: string; def: DefinitionOr|DefinitionAnd; seen: RefSet): bool = result = isRecursive(loc, name, def.field0.pattern0, seen) or isRecursive(loc, name, def.field0.pattern1, seen) for p in def.field0.patternN: if result: return result = isRecursive(loc, name, p, seen) proc isRecursive(loc: Location; name: string; def: Definition; seen: RefSet): bool = case def.orKind of DefinitionKind.or: isRecursive(loc, name, def.or, seen) of DefinitionKind.and: isRecursive(loc, name, def.and, seen) of DefinitionKind.Pattern: isRecursive(loc, name, def.pattern, seen) proc isRecursive(loc: Location; name: string; def: Definition): bool = var seen: RefSet isRecursive(loc, name, def, seen) proc isLiteral(loc: Location; def: Definition): bool {.gcsafe.} proc isLiteral(loc: Location; pat: Pattern): bool {.gcsafe.} proc isLiteral(loc: Location; sp: SimplePattern): bool = case sp.orKind of SimplepatternKind.Ref: if sp.ref.module.len == 0 and not sp.ref.isAtomic: var (loc, def) = deref(loc, sp.ref) result = isLiteral(loc, def) of SimplepatternKind.lit: result = true else: discard proc isLiteral(loc: Location; np: NamedPattern): bool = case np.orKind of NamedPatternKind.named: isLiteral(loc, np.named.pattern) of NamedPatternKind.anonymous: isLiteral(loc, np.anonymous) proc isLiteral(loc: Location; pat: Pattern): bool = case pat.orKind of PatternKind.SimplePattern: isLiteral(loc, pat.simplePattern) of PatternKind.CompoundPattern: false # TODO it could be a compound of all literals proc isLiteral(loc: Location; def: Definition): bool = if def.orKind == DefinitionKind.Pattern: result = isLiteral(loc, def.pattern) proc isRef(sp: SimplePattern): bool = sp.orKind == SimplePatternKind.Ref proc isSimple(pat: Pattern): bool = pat.orKind == PatternKind.SimplePattern proc isLiteral(loc: Location; na: NamedAlternative): bool = isLiteral(loc, na.pattern) proc isSymbolEnum(loc: Location; def: DefinitionOr): bool = result = isLiteral(loc, def.field0.pattern0) and isLiteral(loc, def.field0.pattern1) for na in def.field0.patternN: if not result: break result = isLiteral(loc, na) proc isSymbolEnum(loc: Location; def: Definition): bool = case def.orKind of DefinitionKind.Pattern: if def.pattern.orKind == PatternKind.SimplePattern and def.pattern.simplePattern.orKind == SimplepatternKind.Ref: var (loc, def) = deref(loc, def.pattern.simplePattern.ref) result = isSymbolEnum(loc, def) of DefinitionKind.or: result = isSymbolEnum(loc, def.or) else: discard proc isSymbolEnum(loc: Location; sp: SimplePattern): bool = # HashSet if sp.orKind == SimplepatternKind.Ref: var (loc, def) = deref(loc, sp.ref) result = isSymbolEnum(loc, def) else: discard proc isDictionary(loc: Location; def: Definition): bool {.gcsafe.} proc isDictionary(loc: Location; pat: Pattern): bool = case pat.orKind of PatternKind.SimplePattern: case pat.simplePattern.orKind of SimplePatternKind.Ref: var (loc, def) = deref(loc, pat.simplePattern.ref) result = isDictionary(loc, def) of SimplePatternKind.dictof: result = true else: discard of PatternKind.CompoundPattern: case pat.compoundpattern.orKind of CompoundPatternKind.dict: result = true else: discard proc isDictionary(loc: Location; def: Definition): bool = case def.orKind of DefinitionKind.Pattern: result = isDictionary(loc, def.pattern) of DefinitionKind.or: result = isDictionary(loc, def.or.field0.pattern0.pattern) and isDictionary(loc, def.or.field0.pattern1.pattern) for np in def.or.field0.patternN: if result: result = isDictionary(loc, np.pattern) of DefinitionKind.and: result = isDictionary(loc, def.and.field0.pattern0.pattern) and isDictionary(loc, def.and.field0.pattern1.pattern) for np in def.and.field0.patternN: if result: result = isDictionary(loc, np.pattern) proc isAny(loc: Location; def: Definition): bool = case def.orKind of DefinitionKind.Pattern: case def.pattern.orKind of PatternKind.SimplePattern: case def.pattern.simplePattern.orKind of SimplePatternKind.Ref: var (loc, def) = deref(loc, def.pattern.simplePattern.ref) result = isAny(loc, def) of SimplePatternKind.any: result = true else: discard of PatternKind.CompoundPattern: case def.pattern.compoundpattern.orKind of CompoundPatternKind.rec: result = not isLiteral(loc, def.pattern.compoundpattern.rec.label) else: discard else: discard proc typeIdent(atom: AtomKind): PNode = case atom of AtomKind.Boolean: ident"bool" of AtomKind.Double: ident"float" of AtomKind.Signedinteger: ident"BiggestInt" of AtomKind.String: ident"string" of AtomKind.Bytestring: nkBracketExpr.newTree(ident"seq", ident"byte") of AtomKind.Symbol: ident"Symbol" proc typeIdent(loc: Location; sp: SimplePattern): TypeSpec = case sp.orKind of SimplepatternKind.atom: result = TypeSpec(node: typeIdent(sp.atom.atomKind)) of SimplepatternKind.seqof: result = typeIdent(loc, sp.seqof.pattern) result.node = nkBracketExpr.newTree(ident"seq", result.node) of SimplepatternKind.setof: result = typeIdent(loc, sp.setof.pattern) result.node = if isSymbolEnum(loc, sp.setof.pattern): nkBracketExpr.newTree(ident"set", result.node) else: nkBracketExpr.newTree(ident"HashSet", result.node) of SimplepatternKind.dictof: let key = typeIdent(loc, sp.dictof.key) val = typeIdent(loc, sp.dictof.value) result.node = nkBracketExpr.newTree(ident"Table", key.node, val.node) result.attrs = key.attrs + val.attrs of SimplepatternKind.Ref: result = TypeSpec(node: ident(sp.ref), attrs: attrs(loc, sp)) result.node = parameterize(loc, result) of SimplepatternKind.embedded: if loc.schema.hasEmbeddedType: result = TypeSpec(node: ident"EmbeddedRef") else: result = TypeSpec(node: ident"Value") incl(result.attrs, embedded) of SimplepatternKind.any, SimplepatternKind.lit: result = TypeSpec(node: ident"Value") proc typeIdent(loc: Location; pat: Pattern): TypeSpec = case pat.orKind of PatternKind.SimplePattern: typeIdent(loc, pat.simplePattern) else: raiseAssert "no typeIdent for " & $pat proc toExport(n: sink PNode): PNode = nkPostFix.newNode.add(ident"*", n) proc toStrLit(loc: Location; sp: SimplePattern): PNode {.gcsafe.} proc toStrLit(loc: Location; def: Definition): PNode = if def.orKind == DefinitionKind.Pattern: if def.pattern.orKind == PatternKind.SimplePattern: return toStrLit(loc, def.pattern.simplepattern) raiseAssert "not a string literal" proc toStrLit(loc: Location; sp: SimplePattern): PNode = case sp.orKind of SimplePatternKind.lit: result = PNode(kind: nkStrLit, strVal: $sp.lit.value) of SimplePatternKind.Ref: var (loc, def) = deref(loc, sp.ref) result = toStrLit(loc, def) of SimplePatternKind.embedded: doAssert not loc.schema.hasEmbeddedType result = PNode(kind: nkStrLit, strVal: "#:" & toStrLit(loc, sp.embedded.interface).strVal) else: raiseAssert $sp proc toFieldIdent(s: string): PNode = nkPostFix.newTree(ident("*"), nkAccQuoted.newTree(ident(s))) proc toFieldIdent(loc: Location, label: string; pat: Pattern): PNode = result = label.toFieldIdent if isLiteral(loc, pat): result = nkPragmaExpr.newTree( result, nkPragma.newTree( nkExprColonExpr.newTree( ident"preservesLiteral", toStrLit(loc, pat.simplePattern)))) proc newEmpty(): PNode = newNode(nkEmpty) proc embeddingParams(loc: Location; embeddable: bool): PNode = newEmpty() proc identDef(scm: Schema; a, b: PNode; embeddable: bool): PNode = nkIdentDefs.newTree(a, b, newEmpty()) proc identDef(scm: Schema; l: PNode; ts: TypeSpec): PNode = identDef(scm, l, ts.node, ts.isEmbedded) proc label(pat: Pattern): string = raiseAssert "need to derive record label for " & $pat proc label(na: NamedPattern; parentLabel: string; index: int): string = case na.orKind of NamedPatternKind.named: string na.named.name of NamedPatternKind.anonymous: "field" & $index proc label(nsp: NamedSimplePattern; parentLabel: string; index: int): string = case nsp.orKind of NamedSimplePatternKind.named: string nsp.named.name of NamedSimplePatternKind.anonymous: parentLabel & $index 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 = string 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: string np.named.name of NamedPatternKind.anonymous: np.anonymous.idStr proc typeDef(loc: Location; name: string; pat: SimplePattern; ty: PNode): PNode = let id = name.ident.toExport nkTypeDef.newTree(id, newEmpty(), ty) proc typeDef(loc: Location; name: string; pat: Pattern; ty: PNode): PNode = let embedParams = embeddingParams(loc, isEmbedded(loc, pat)) id = name.ident.toExport case pat.orKind of PatternKind.CompoundPattern: let pragma = newNode(nkPragma) case pat.compoundPattern.orKind of CompoundPatternKind.rec: if isLiteral(loc, pat.compoundPattern.rec.label): pragma.add( nkExprColonExpr.newTree( ident"preservesRecord", PNode(kind: nkStrLit, strVal: pat.compoundPattern.rec.label.idStr))) nkTypeDef.newTree(nkPragmaExpr.newTree(id, pragma), embedParams, ty) elif pragma.len > 0: nkTypeDef.newTree(nkPragmaExpr.newTree(id, pragma), embedParams, ty) else: nkTypeDef.newTree(id, embedParams, ty) of CompoundPatternKind.tuple, CompoundPatternKind.tuplePrefix: pragma.add(ident"preservesTuple") nkTypeDef.newTree(nkPragmaExpr.newTree(id, pragma), embedParams, ty) of CompoundPatternKind.dict: pragma.add(ident"preservesDictionary") nkTypeDef.newTree(nkPragmaExpr.newTree(id, pragma), embedParams, ty) else: nkTypeDef.newTree(id, embedParams, ty) proc typeDef(loc: Location; name: string; def: Definition; ty: PNode): PNode = case def.orKind of DefinitionKind.or: var ty = ty let pragma = newNode(nkPragma) if isRecursive(loc, name, def): doAssert ty.kind == nkObjectTy pragma.add(ident"acyclic") ty = nkRefTy.newTree(ty) pragma.add(ident"preservesOr") if isSymbolEnum(loc, def): pragma.add ident"pure" nkTypeDef.newTree( nkPragmaExpr.newTree( name.ident.accQuote.toExport, pragma), embeddingParams(loc, isEmbedded(loc, def)), ty) of DefinitionKind.and: var pragma = nkPragma.newNode if isDictionary(loc, def): pragma.add(ident"preservesDictionary") nkTypeDef.newTree( nkPragmaExpr.newTree( name.ident.accQuote.toExport, pragma), embeddingParams(loc, isEmbedded(loc, def)), ty) of DefinitionKind.Pattern: typeDef(loc, name, def.pattern, ty) proc nimTypeOf(loc: Location; known: var TypeTable; name: string; nsp: NamedSimplePattern): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; name: string; pat: Pattern): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; name: string; cp: CompoundPattern): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; name: string; sp: SimplePattern): TypeSpec = typeIdent(loc, sp) proc addField(recList: PNode; loc: Location; known: var TypeTable; sp: SimplePattern; label: string): PNode {.discardable.} = let scm = loc.schema id = label.toFieldIdent if isLiteral(loc, sp): let id = nkPragmaExpr.newTree( id, nkPragma.newTree( nkExprColonExpr.newTree( ident"preservesLiteral", toStrLit(loc, sp)))) recList.add identDef(scm, id, TypeSpec(node: ident"tuple[]")) elif sp.orKind == SimplePatternKind.embedded: let id = nkPragmaExpr.newTree( id, nkPragma.newTree(ident"preservesEmbedded")) recList.add identDef(scm, id, nimTypeOf(loc, known, "", sp)) else: recList.add identDef(scm, id, nimTypeOf(loc, known, "", sp)) proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: string; np: NamedPattern; index = 0) = let label = label(np, parentName, index) id = label.toFieldIdent pattern = np.pattern if pattern.isSimple: addField(recList, loc, known, pattern.simplePattern, label) else: var typeName = parentName & capitalizeAscii(label) typePath = loc.schemaPath & @[Symbol typeName] fieldSpec = nimTypeOf(loc, known, label, pattern) known[typePath] = typeDef(loc, typeName, pattern, fieldSpec.node) recList.add identDef(loc.schema, id, ident(typeName), isEmbedded(loc, pattern)) proc addField(recList: PNode; loc: Location; known: var TypeTable; parentName: string; nsp: NamedSimplePattern; index: int; optional: bool) = let label = label(nsp, parentName, index) id = label.toFieldIdent pattern = nsp.pattern if pattern.isRef: var node = typeIdent(loc, pattern).node if optional: node = nkBracketExpr.newTree(ident"Option", node) recList.add identDef(loc.schema, id, node, false) else: var typeName = parentName & capitalizeAscii(label) typePath = loc.schemaPath & @[Symbol typeName] fieldSpec = nimTypeOf(loc, known, label, pattern) if optional: fieldSpec.node = nkBracketExpr.newTree(ident"Option", fieldSpec.node) known[typePath] = typeDef(loc, typeName, pattern, fieldSpec.node) recList.add identDef(loc.schema, id, fieldSpec.node, false) proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName: string; cp: CompoundPattern): PNode {.discardable.} = let scm = loc.schema template addField(np: NamedPattern; index: int) = let label = label(np, parentName, index) id = label.toFieldIdent pattern = np.pattern if pattern.isSimple: addField(recList, loc, known, pattern.simplePattern, label) else: var typeName = parentName & capitalizeAscii(label) typePath = loc.schemaPath & @[Symbol typeName] fieldSpec = nimTypeOf(loc, known, label, pattern) known[typePath] = typeDef(loc, typeName, pattern, fieldSpec.node) recList.add identDef(scm, id, ident(typeName), isEmbedded(loc, pattern)) case cp.orKind of CompoundPatternKind.rec: # recList.add identDef(scm, ident(label), nimTypeOf(loc, known, "", cp)) raiseassert "unexpected record of fields " #& $cp.rec of CompoundPatternKind.tuple: for i, np in cp.tuple.patterns: addField(np, i) of CompoundPatternKind.tuplePrefix: for i, np in cp.tuplePrefix.fixed: addField(np, i) let variableType = nimTypeOf(loc, known, "", cp.tuplePrefix.variable) recList.add identDef( scm, nkPragmaExpr.newTree( ident(cp.tuplePrefix.variable, parentName).accQuote.toExport, nkPragma.newTree(ident"preservesTupleTail")), parameterize(loc, variableType), variableType.isEmbedded) of CompoundPatternKind.dict: for nameVal, nsp in cp.dict.entries: recList.addField(loc, known, $nameVal, nsp, 0, false) reclist proc addFields(recList: PNode; loc: Location; known: var TypeTable; name: string; pat: SimplePattern): PNode {.discardable.} = addField(recList, loc, known, pat, name) proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName: string; pat: Pattern): PNode {.discardable.} = case pat.orKind of PatternKind.SimplePattern: discard addFields(recList, loc, known, parentName, pat.simplePattern) of PatternKind.CompoundPattern: discard addFields(recList, loc, known, parentName, pat.compoundPattern) reclist proc addFields(recList: PNode; loc: Location; known: var TypeTable; parentName: string; entries: DictionaryEntries): PNode {.discardable.} = var sortedEntries = initOrderedTable[Value, NamedSimplePattern](entries.len) for key, val in entries.pairs: sortedEntries[key] = val sort(sortedEntries) do (x, y: (Value, NamedSimplePattern)) -> int: cmp(x[0], y[0]) for key, val in sortedEntries.pairs: doAssert(key.isSymbol) let label = string key.symbol addField(recList, loc, known, val.pattern, label) recList proc nimTypeOf(loc: Location; known: var TypeTable; name: string; nsp: NamedSimplePattern): TypeSpec = case nsp.orKind of NamedsimplepatternKind.named: nimTypeOf(loc, known, string nsp.named.name, nsp.named.pattern) of NamedsimplepatternKind.anonymous: nimTypeOf(loc, known, name, nsp.anonymous) proc nimTypeOf(loc: Location; known: var TypeTable; name: string; rec: CompoundPatternRec): TypeSpec = if isLiteral(loc, rec.label): result.node = nkObjectTy.newTree( newEmpty(), newEmpty(), newNode(nkRecList).addFields(loc, known, name, rec.fields.pattern)) else: result.node = ident"Value" proc nimTypeOf(loc: Location; known: var TypeTable; name: string; cp: CompoundPattern): TypeSpec = case cp.orKind of CompoundPatternKind.`rec`: result = nimTypeOf(loc, known, name, cp.rec) of CompoundPatternKind.`tuple`, CompoundPatternKind.`tupleprefix`: result.node = nkObjectTy.newTree( newEmpty(), newEmpty(), newNode(nkRecList).addFields(loc, known, name, cp)) of CompoundPatternKind.`dict`: result.node = nkObjectTy.newTree(newEmpty(), newEmpty(), newNode(nkRecList).addFields(loc, known, name, cp.dict.entries)) proc nimTypeOf(loc: Location; known: var TypeTable; name: string; pat: Pattern): TypeSpec = case pat.orKind of PatternKind.SimplePattern: nimTypeOf(loc, known, name, pat.simplePattern) of PatternKind.CompoundPattern: nimTypeOf(loc, known, name, pat.compoundPattern) proc nimTypeOf(loc: Location; known: var TypeTable; name: string; orDef: DefinitionOr): TypeSpec = proc toEnumTy(): PNode = let ty = nkEnumTy.newNode.add newEmpty() proc add (na: NamedAlternative) = ty.add na.variantLabel.ident.accQuote add(orDef.field0.pattern0) add(orDef.field0.pattern1) for na in orDef.field0.patternN: add(na) ty if isSymbolEnum(loc, orDef): result.node = toEnumTy() else: let enumName = name & "Kind" enumPath = loc.schemaPath & @[Symbol enumName] enumIdent = ident(enumName) if enumPath notin known: known[enumPath] = nkTypeDef.newTree( nkPragmaExpr.newTree( enumName.ident.toExport, nkPragma.newTree(ident"pure")), newEmpty(), toEnumTy()) let recCase = nkRecCase.newNode.add( nkIdentDefs.newNode.add( "orKind".ident.toExport, enumName.ident, newEmpty())) template addCase(na: NamedAlternative) = let branchRecList = newNode(nkRecList) var memberType: TypeSpec if isLiteral(loc, na.pattern): memberType.node = ident"bool" elif na.pattern.isSimple: memberType = typeIdent(loc, na.pattern) else: let memberTypeName = name & na.variantLabel.capitalizeAscii memberPath = loc.schemaPath & @[Symbol memberTypeName] memberType.node = ident memberTypeName let ty = nimTypeOf(loc, known, memberTypeName, na.pattern) addAttrs(memberType, ty) if memberPath notin known and not isLiteral(loc, na.pattern): known[memberPath] = typeDef(loc, memberTypeName, na.pattern, ty.node) addAttrs(result, memberType) memberType.node = parameterize( loc, memberType.node, isEmbedded(loc, na.pattern)) var memberId = toFieldIdent(loc, na.variantLabel.normalize, na.pattern) if isEmbedded(loc, na.pattern): memberId = nkPragmaExpr.newTree( memberId, nkPragma.newTree(ident"preservesEmbedded")) branchRecList.add nkIdentDefs.newTree( memberId, memberType.node, newEmpty()) recCase.add nkOfBranch.newTree( nkDotExpr.newTree( enumIdent, na.variantLabel.ident.accQuote), branchRecList) addCase(orDef.field0.pattern0) addCase(orDef.field0.pattern1) for na in orDef.field0.patternN: addCase(na) result.node = nkObjectTy.newTree( newEmpty(), newEmpty(), nkRecList.newTree(recCase)) proc isAny(sp: SimplePattern): bool = sp.orKind == SimplePatternKind.any proc initSimpleAny: SimplePattern = SimplePattern(orKind: SimplePatternKind.any) proc asAny(nsp: NamedSimplePattern): NamedSimplePattern = result = nsp case result.orKind of NamedSimplePatternKind.named: if not result.named.pattern.isAny: result.named.pattern = initSimpleAny() of NamedSimplePatternKind.anonymous: if not result.anonymous.isAny: result.anonymous = initSimpleAny() type AndEntry = tuple[pattern: NamedSimplePattern, optional: bool] AndEntries = OrderedTable[Value, AndEntry] proc collect(entries: var AndEntries; loc: Location; def: Definition; optional: bool) {.gcsafe.} proc collect(entries: var AndEntries; loc: Location; pat: SimplePattern; optional: bool) = case pat.orKind of SimplePatternKind.Ref: let (loc, def) = deref(loc, pat.ref) collect(entries, loc, def, optional) else: raiseAssert "cannot collect dictionary entries from " & $pat proc collect(entries: var AndEntries; loc: Location; comp: CompoundPattern; optional: bool) = case comp.orKind of CompoundPatternKind.dict: for key, nsp in comp.dict.entries.pairs: if entries.hasKey(key): entries[key] = (asAny nsp, optional) else: entries[key] = (nsp, optional) else: raiseAssert "cannot collect dictionary entries from " & $comp proc collect(entries: var AndEntries; loc: Location; pat: Pattern; optional: bool) = case pat.orKind of PatternKind.SimplePattern: collect(entries, loc, pat.simplepattern, optional) of PatternKind.CompoundPattern: collect(entries, loc, pat.compoundpattern, optional) proc collect(entries: var AndEntries; loc: Location; def: Definition; optional: bool) = case def.orKind of DefinitionKind.or: collect(entries, loc, def.or.field0.pattern0.pattern, true) collect(entries, loc, def.or.field0.pattern1.pattern, true) for np in def.or.field0.patternN: collect(entries, loc, np.pattern, true) of DefinitionKind.and: collect(entries, loc, def.and.field0.pattern0.pattern, optional) collect(entries, loc, def.and.field0.pattern1.pattern, optional) for np in def.and.field0.patternN: collect(entries, loc, np.pattern, optional) of DefinitionKind.Pattern: collect(entries, loc, def.pattern, optional) proc toDef(a: DefinitionAnd): Definition = Definition(orKind: DefinitionKind.and, `and`: a) proc nimTypeOf(loc: Location; known: var TypeTable; name: string; andDef: DefinitionAnd): TypeSpec = var def = andDef.toDef if isDictionary(loc, def): var recList = nkRecList.newNode entries: AndEntries collect(entries, loc, def, false) sort(entries) do (x, y: (Value, AndEntry)) -> int: preserves.cmp(x[0], y[0]) # TODO: sort the entries var i = 0 for key, (nsp, opt) in entries.pairs: recList.addField(loc, known, name, nsp, i, opt) inc(i) result.node = nkObjectTy.newTree( newEmpty(), newEmpty(), recList) else: result.node = ident"Value" proc nimTypeOf(loc: Location; known: var TypeTable; name: string; def: Definition): TypeSpec = case def.orKind of DefinitionKind.or: nimTypeOf(loc, known, name, def.or) of DefinitionKind.and: nimTypeOf(loc, known, name, def.and) of DefinitionKind.Pattern: nimTypeOf(loc, known, name, def.pattern) proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: Definition) = discard proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; def: Definition) = discard proc collectRefImports(imports: var StringSet; loc: Location; pat: Pattern) proc collectRefImports(imports: var StringSet; loc: Location; sp: SimplePattern) = case sp.orKind of SimplePatternKind.seqof: collectRefImports(imports, loc, sp.seqof.pattern) of SimplePatternKind.setof: incl(imports, "std/sets") collectRefImports(imports, loc, sp.setof.pattern) of SimplePatternKind.dictof: incl(imports, "std/tables") collectRefImports(imports, loc, sp.dictof.key) collectRefImports(imports, loc, sp.dictof.value) of SimplePatternKind.Ref: if sp.ref.module != @[] and sp.ref.module != loc.schemaPath: incl(imports, string sp.ref.module[0]) else: discard proc collectRefImports(imports: var StringSet; loc: Location; cp: CompoundPattern) = case cp.orKind of CompoundPatternKind.rec: collectRefImports(imports, loc, cp.rec.label.pattern) collectRefImports(imports, loc, cp.rec.fields.pattern) of CompoundPatternKind.tuple: for p in cp.tuple.patterns: collectRefImports(imports, loc, p.pattern) of CompoundPatternKind.tupleprefix: for np in cp.tupleprefix.fixed: collectRefImports(imports, loc, np.pattern) collectRefImports(imports, loc, cp.tupleprefix.variable.pattern) of CompoundPatternKind.dict: for nsp in cp.dict.entries.values: collectRefImports(imports, loc, nsp.pattern) proc collectRefImports(imports: var StringSet; loc: Location; pat: Pattern) = case pat.orKind of PatternKind.SimplePattern: collectRefImports(imports, loc, pat.simplePattern) of PatternKind.CompoundPattern: collectRefImports(imports, loc, pat.compoundPattern) proc collectRefImports(imports: var StringSet; loc: Location; def: Definition) = case def.orKind of DefinitionKind.or: collectRefImports(imports, loc, def.or.field0.pattern0.pattern) collectRefImports(imports, loc, def.or.field0.pattern1.pattern) for na in def.or.field0.patternN: collectRefImports(imports, loc, na.pattern) of DefinitionKind.and: if isDictionary(loc, def): incl(imports, "std/options") collectRefImports(imports, loc, def.and.field0.pattern0.pattern) collectRefImports(imports, loc, def.and.field0.pattern1.pattern) for np in def.and.field0.patternN: collectRefImports(imports, loc, np.pattern) of DefinitionKind.Pattern: collectRefImports(imports, loc, def.pattern) proc collectRefImports(imports: var StringSet; loc: Location; scm: Schema) = for _, def in scm.field0.definitions: collectRefImports(imports, loc, def) proc mergeType(x: var PNode; y: PNode) = if x.isNil: x = y else: x = nkInfix.newTree(ident"|", x, y) proc hasPrefix(a, b: ModulePath): bool = for i, e in b: if i > a.high or a[i] != e: return false true proc renderNimBundle*(bundle: Bundle): Table[string, string] = ## Render Nim modules from a `Bundle`. result = initTable[string, string](bundle.modules.len) var typeDefs: TypeTable for scmPath, scm in bundle.modules: let loc = (bundle, scmPath) var typeSection = newNode nkTypeSection procs: seq[PNode] unembeddableType: PNode for name, def in scm.field0.definitions.pairs: if isLiteral(loc, def): generateConstProcs(procs, scm, string name, def) else: var name = string name name[0] = name[0].toUpperAscii var defIdent = parameterize(loc, ident(name), isEmbedded(loc, def)) if not isSymbolEnum(loc, def) and not isAny(loc, def): mergeType(unembeddableType, defIdent) let typeSpec = nimTypeOf(loc, typeDefs, name, def) typeDefs[scmPath & @[Symbol name]] = typeDef(loc, name, def, typeSpec.node) generateProcs(procs, scm, name, def) for typePath, typeDef in typeDefs.pairs: if typepath.hasPrefix(scmPath): add(typeSection, typeDef) let imports = nkImportStmt.newNode.add(ident"preserves") block: var importSet: HashSet[string] collectRefImports(importSet, loc, scm) for module in importSet: add(imports, ident(module)) if not unembeddableType.isNil: procs.add nkProcDef.newTree( "$".toFieldIdent, newEmpty(), newEmpty(), nkFormalParams.newTree( ident"string", nkIdentDefs.newTree( ident"x", unembeddableType, newEmpty())), newEmpty(), newEmpty(), nkStmtList.newTree( nkCall.newTree(ident"$", nkCall.newTree(ident"toPreserves", ident"x")))) procs.add nkProcDef.newTree( "encode".ident.toExport, newEmpty(), newEmpty(), nkFormalParams.newTree( nkBracketExpr.newTree(ident"seq", ident"byte"), nkIdentDefs.newTree( ident"x", unembeddableType, newEmpty())), newEmpty(), newEmpty(), nkStmtList.newTree( nkCall.newTree(ident"encode", nkCall.newTree( ident"toPreserves", ident"x")))) var module = newNode(nkStmtList).add( imports, typeSection ).add(procs) var filePath = "" for p in scmPath: if filePath != "": add(filePath, '/') add(filePath, string p) add(filePath, ".nim") result[filePath] = renderTree(module, {renderIds, renderSyms, renderIr, renderNonExportedFields, renderExpandUsing}) # not sure what all these flags do when isMainModule: import ./schemaparse proc writeModules(bundle: Bundle) = let modules = renderNimBundle(bundle) for path, txt in modules.pairs: writeFile(path, txt) stdout.writeLine(path) import std/[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 inputPath in inputs: var bundle: Bundle if dirExists inputPath: for filePath in walkDirRec(inputPath, relative = true): var (dirPath, fileName, fileExt) = splitFile(filePath) if fileExt == ".prs": var scm = parsePreservesSchema(readFile(inputPath / filePath), inputPath / dirPath) path: ModulePath for e in split(dirPath, '/'): add(path, Symbol e) add(path, Symbol fileName) bundle.modules[path] = scm elif fileExists inputPath: var (dirPath, fileName, _) = splitFile inputPath let raw = readFile inputPath if raw[0] == 0xb4.char: var pr = decodePreserves raw if not fromPreserves(bundle, pr): var schema: Schema if fromPreserves(schema, pr): bundle.modules[@[Symbol fileName]] = schema else: var scm = parsePreservesSchema(readFile(inputPath), dirPath) bundle.modules[@[Symbol fileName]] = scm if bundle.modules.len == 0: quit "Failed to recognize " & inputPath else: writeModules(bundle)