# 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 --path:.. -r ./preserves_schema_nim ../../../preserves/schema/schema.bin import std/[hashes, strutils, sets, tables] import compiler/[ast, idents, renderer, lineinfos] import ../preserves, ./schema type Value = Preserve[void] Attribute = enum embedded ## type contains an embedded value and ## must take an parameter recursive ## type is recursive and therefore must be a ref Attributes = set[Attribute] TypeSpec = object node: PNode attrs: Attributes TypeTable = OrderedTable[schema.ModulePath, PNode] Location = tuple[bundle: Bundle, schemaPath: ModulePath] 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 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(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 isPreserve(n: PNode): bool = n.kind == nkBracketExpr and n.renderTree == "preserves.Value" proc isEmbedded(ts: TypeSpec): bool = embedded in ts.attrs proc isRecursive(ts: TypeSpec): bool = recursive in ts.attrs 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 = nn(nkDotExpr, result, id) proc ident(`ref`: Ref): PNode = for m in`ref`.module: dotExtend(result, string m) dotExtend(result, `ref`.name.string.capitalizeAscii) proc deref(loc: Location; r: Ref): (Location, Definition) = result[0] = loc if r.module == @[]: result[1] = loc.bundle.modules[loc.schemaPath].data.definitions[r.name] else: result[0].schemaPath = r.module result[1] = loc.bundle.modules[r.module].data.definitions[r.name] proc hasEmbeddedType(scm: Schema): bool = case scm.data.embeddedType.orKind of EmbeddedtypenameKind.`false`: false of EmbeddedtypenameKind.`Ref`: true proc embeddedIdentString(scm: Schema): string = case scm.data.embeddedType.orKind of EmbeddedtypenameKind.`false`: raiseAssert "no embedded type for this module" of EmbeddedtypenameKind.`Ref`: doAssert $scm.data.embeddedType.ref.name != "" $scm.data.embeddedType.ref.name proc embeddedIdent(scm: Schema): PNode = ident(embeddedIdentString(scm)) proc preserveIdent(scm: Schema): Pnode = if scm.hasEmbeddedType: nn(nkBracketExpr, ident"Preserve", embeddedIdent(scm)) else: nn(nkBracketExpr, ident"Preserve", ident"void") proc parameterize(scm: Schema; node: PNode; embeddable: bool): PNode = if embeddable and node.kind notin {nkBracketExpr}: nn(nkBracketExpr, node, scm.embeddedIdent) else: node proc parameterize(scm: Schema; spec: TypeSpec): PNode = parameterize(scm, spec.node, spec.isEmbedded) proc hash(r: Ref): Hash = r.toPreserve.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 step(loc: Location; r: Ref): Location = (loc.bundle, r.module) proc attrs(loc: Location; sp: SimplePattern; seen: RefSet): Attributes = case sp.orKind of SimplepatternKind.`atom`, SimplepatternKind.`lit`: {} of SimplepatternKind.`any`, SimplepatternKind.`embedded`: if loc.schema.hasEmbeddedType: {embedded} else: {} 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: {recursive} 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`: discard 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; orDef: DefinitionOr; seen: RefSet): Attributes = result = attrs(loc, orDef.data.pattern0, seen) + attrs(loc, orDef.data.pattern1, seen) for p in orDef.data.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.data.pattern0, seen) + attrs(loc, def.and.data.pattern1, seen) for p in def.and.data.patternN: result = result + attrs(loc, p, seen) of DefinitionKind.`Pattern`: result = attrs(loc, def.pattern, seen) proc attrs(loc: Location; p: Definition|DefinitionOr|Pattern|CompoundPattern|SimplePattern): Attributes = var seen: RefSet attrs(loc, p, seen) proc isEmbedded(loc: Location; p: Definition|DefinitionOr|Pattern|CompoundPattern): bool = embedded in attrs(loc, p) proc isRecursive(loc: Location; p: Definition|DefinitionOr|Pattern|CompoundPattern): bool = recursive in attrs(loc, p) proc isLiteral(loc: Location; def: Definition): bool {.gcsafe.} proc isLiteral(loc: Location; sp: SimplePattern): bool = case sp.orKind of SimplepatternKind.Ref: if sp.ref.module.len == 0: var (loc, def) = deref(loc, sp.ref) result = isLiteral(loc, def) of SimplepatternKind.lit: result = true of SimplepatternKind.embedded: result = isLiteral(loc, sp.embedded.interface) else: discard 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 isRef(pat: Pattern): bool = pat.orKind == PatternKind.SimplePattern and pat.simplePattern.isRef proc isSimple(pat: Pattern): bool = pat.orKind == PatternKind.SimplePattern proc isLiteral(loc: Location; na: NamedAlternative): bool = isLiteral(loc, na.pattern) proc isSymbolEnum(loc: Location; orDef: DefinitionOr): bool = result = isLiteral(loc, orDef.data.pattern0) and isLiteral(loc, orDef.data.pattern1) for na in orDef.data.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 isAny(loc: Location; def: Definition): bool = if def.orKind == DefinitionKind.Pattern: if def.pattern.orKind == 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 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"Symbol" proc typeIdent(loc: Location; sp: SimplePattern): TypeSpec = let scm = loc.schema case sp.orKind of SimplepatternKind.`atom`: result = TypeSpec(node: typeIdent(sp.atom.atomKind)) of SimplepatternKind.`seqof`: result = typeIdent(loc, sp.seqof.pattern) result.node = nn(nkBracketExpr, ident"seq", result.node) of SimplepatternKind.`setof`: result = typeIdent(loc, sp.setof.pattern) result.node = if isSymbolEnum(loc, sp.setof.pattern): nn(nkBracketExpr, ident"set", result.node) else: nn(nkBracketExpr, ident"HashSet", result.node) of SimplepatternKind.`dictof`: let key = typeIdent(loc, sp.dictof.key) val = typeIdent(loc, sp.dictof.value) result.node = nn(nkBracketExpr, 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(scm, result) of SimplepatternKind.`embedded`: case scm.data.embeddedType.orKind of EmbeddedtypenameKind.`false`: result = typeIdent(loc, sp.embedded.interface) of EmbeddedtypenameKind.`Ref`: result = TypeSpec(node: scm.embeddedIdent()) incl(result.attrs, embedded) of SimplepatternKind.`any`, SimplepatternKind.`lit`: result = TypeSpec(node: preserveIdent(scm)) 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: result = PNode(kind: nkStrLit, strVal: "#!" & $sp.embedded.interface) else: raiseAssert $sp proc toFieldIdent(s: string): PNode = nn(nkPostFix, ident("*"), nn(nkAccQuoted, ident(s))) proc toFieldIdent(loc: Location, label: string; pat: Pattern): PNode = result = label.toFieldIdent if isLiteral(loc, pat): result = nn(nkPragmaExpr, result, nn(nkPragma, nn(nkExprColonExpr, ident"preservesLiteral", toStrLit(loc, pat.simplePattern)))) proc newEmpty(): PNode = newNode(nkEmpty) proc embeddingParams(scm: Schema; embeddable: bool): PNode = if embeddable: nn(nkGenericParams, nn(nkIdentDefs, embeddedIdent(scm), newEmpty(), newEmpty())) else: newEmpty() proc identDef(scm: Schema; a, b: PNode; embeddable: bool): PNode = if embeddable and b.kind notin {nkBracketExpr, nkTupleTy} and (b.kind != nkIdent or b.ident.s != scm.embeddedIdentString): nn(nkIdentDefs, a, nn(nkBracketExpr, b, embeddedIdent(scm)), newEmpty()) else: nn(nkIdentDefs, 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): string = case na.orKind of NamedPatternKind.`named`: string 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 = 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: Pattern; ty: PNode): PNode = let scm = loc.schema embedParams = embeddingParams(scm, isEmbedded(loc, pat)) id = name.ident.toExport case pat.orKind of 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(loc: Location; name: string; def: Definition; ty: PNode): PNode = case def.orKind of DefinitionKind.or: let pragma = nn(nkPragma, ident"preservesOr") if isSymbolEnum(loc, def): pragma.add ident"pure" nn(nkTypeDef, nn(nkPragmaExpr, name.ident.accQuote.toExport, pragma), embeddingParams(loc.schema, isEmbedded(loc, def)), ty) of DefinitionKind.and: raiseAssert "And variants not suported" of DefinitionKind.Pattern: typeDef(loc, name, def.pattern, ty) proc nimTypeOf(loc: Location; known: var TypeTable; nsp: NamedSimplePattern; name = ""): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; pat: Pattern; name = ""): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; cp: CompoundPattern; name = ""): TypeSpec proc nimTypeOf(loc: Location; known: var TypeTable; sp: SimplePattern; name = ""): 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 = nn(nkPragmaExpr, id, nn(nkPragma, nn(nkExprColonExpr, ident"preservesLiteral", toStrLit(loc, sp)))) recList.add identDef(scm, id, TypeSpec(node: ident"bool")) elif sp.orKind == SimplePatternKind.embedded and not scm.hasEmbeddedType: let id = nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesEmbedded")) recList.add identDef(scm, id, nimTypeOf(loc, known, sp)) else: recList.add identDef(scm, id, nimTypeOf(loc, known, sp)) proc addFields(recList: PNode; loc: Location; known: var TypeTable; cp: CompoundPattern; parentName: string): PNode {.discardable.} = let scm = loc.schema template addField(np: NamedPattern) = let label = np.label id = label.toFieldIdent pattern = np.pattern if pattern.isRef or pattern.isSimple: addField(recList, loc, known, pattern.simplePattern, label) else: var typeName = parentName & capitalizeAscii(label) typePath = loc.schemaPath & @[Symbol typeName] fieldSpec = nimTypeOf(loc, known, pattern, label) 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 np in cp.tuple.patterns: addField(np) of CompoundPatternKind.tuplePrefix: for np in cp.tuplePrefix.fixed: addField(np) let variableType = nimTypeOf(loc, known, cp.tuplePrefix.variable) recList.add identDef( scm, nn(nkPragmaExpr, ident(cp.tuplePrefix.variable, parentName).accQuote.toExport, nn(nkPragma, ident"preservesTupleTail")), parameterize(scm, variableType), variableType.isEmbedded) else: raiseAssert "not adding fields for " #& $cp reclist proc addFields(recList: PNode; loc: Location; known: var TypeTable; pat: Pattern; parentName: string): PNode {.discardable.} = case pat.orKind of PatternKind.SimplePattern: addField(recList, loc, known, pat.simplePattern, "data") of PatternKind.CompoundPattern: discard addFields(recList, loc, known, pat.compoundPattern, parentName) reclist proc addFields(recList: PNode; loc: Location; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} = for key, val in entries.pairs: doAssert(key.isSymbol) let label = string key.symbol addField(recList, loc, known, val.pattern, label) recList proc nimTypeOf(loc: Location; known: var TypeTable; nsp: NamedSimplePattern; name: string): TypeSpec = case nsp.orKind of NamedsimplepatternKind.named: nimTypeOf(loc, known, nsp.named.pattern, string nsp.named.name) of NamedsimplepatternKind.anonymous: nimTypeOf(loc, known, nsp.anonymous, name) proc nimTypeOf(loc: Location; known: var TypeTable; cp: CompoundPattern; name: string): TypeSpec = case cp.orKind of CompoundPatternKind.`rec`: result.node = nn(nkObjectTy, newEmpty(), newEmpty(), nn(nkRecList).addFields(loc, known, cp.rec.fields.pattern, name)) of CompoundPatternKind.`tuple`, CompoundPatternKind.`tupleprefix`: result.node = nn(nkObjectTy, newEmpty(), newEmpty(), nn(nkRecList).addFields(loc, known, cp, name)) of CompoundPatternKind.`dict`: result.node = nn(nkObjectTy, newEmpty(), newEmpty(), nn(nkRecList).addFields(loc, known, cp.dict.entries, name)) if result.node.kind == nkObjectTy and isRecursive(loc, cp): result.node = nn(nkRefTy, result.node) proc nimTypeOf(loc: Location; known: var TypeTable; pat: Pattern; name: string): TypeSpec = case pat.orKind of PatternKind.SimplePattern: nimTypeOf(loc, known, pat.simplePattern, name) of PatternKind.CompoundPattern: nimTypeOf(loc, known, pat.compoundPattern, name) proc nimTypeOf(loc: Location; known: var TypeTable; orDef: DefinitionOr; name: string): TypeSpec = let scm = loc.schema 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(loc, orDef): result.node = toEnumTy() else: let enumName = name & "Kind" enumPath = loc.schemaPath & @[Symbol enumName] enumIdent = ident(enumName) if enumPath notin known: known[enumPath] = 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(loc, na.pattern): memberType.node = ident"bool" elif na.pattern.isRef: 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, na.pattern, memberTypeName) 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( scm, memberType.node, isEmbedded(loc, na.pattern)) branchRecList.add nn(nkIdentDefs, toFieldIdent(loc, 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(nkObjectTy, newEmpty(), newEmpty(), nn(nkRecList, recCase)) # result.attrs = attrs(loc, orDef) if result.node.kind == nkObjectTy and (recursive in attrs(loc, orDef)): result.node = nn(nkRefTy, result.node) proc nimTypeOf(loc: Location; known: var TypeTable; def: Definition; name: string): TypeSpec = case def.orKind of DefinitionKind.or: nimTypeOf(loc, known, def.or, name) of DefinitionKind.and: raiseAssert """"And" definitions are unsupported""" of DefinitionKind.Pattern: nimTypeOf(loc, known, def.pattern, name) proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: Definition) = discard proc literalToPreserveCall(scm: Schema; pr: Value): 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.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, "BiggestInt", 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, string 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(string 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 mergeType(x: var PNode; y: PNode) = if x.isNil: x = y else: x = nn(nkInfix, 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: var typeSection = newNode nkTypeSection procs: seq[PNode] unembeddableType, embeddableType: PNode for name, def in scm.data.definitions.pairs: let loc = (bundle, scmPath) if isLiteral(loc, def): generateConstProcs(procs, scm, string name, def) else: var name = string name name[0] = name[0].toUpperAscii var defIdent = parameterize(scm, ident(name), isEmbedded(loc, def)) if not isSymbolEnum(loc, def) and not isAny(loc, def): if isEmbedded(loc, def): mergeType(embeddableType, defIdent) else: mergeType(unembeddableType, defIdent) let typeSpec = nimTypeOf(loc, typeDefs, def, name) 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) var imports = nkImportStmt.newNode.add( ident"std/typetraits", ident"preserves") collectRefImports(imports, scm) if not embeddableType.isNil: let genericParams = nn(nkGenericParams, nn(nkIdentDefs, embeddedIdent(scm), newEmpty(), newEmpty())) procs.add nn(nkProcDef, "$".toFieldIdent, newEmpty(), genericParams, nn(nkFormalParams, ident"string", nn(nkIdentDefs, ident"x", embeddableType, newEmpty())), newEmpty(), newEmpty(), nn(nkStmtList, nn(nkCall, ident"$", nn(nkCall, ident"toPreserve", ident"x", embeddedIdent(scm))))) procs.add nn(nkProcDef, "encode".ident.toExport, newEmpty(), genericParams, nn(nkFormalParams, nn(nkBracketExpr, ident"seq", ident"byte"), nn(nkIdentDefs, ident"x", embeddableType, newEmpty())), newEmpty(), newEmpty(), nn(nkStmtList, nn(nkCall, ident"encode", nn(nkCall, ident"toPreserve", ident"x", embeddedIdent(scm))))) if not unembeddableType.isNil: procs.add nn(nkProcDef, "$".toFieldIdent, newEmpty(), newEmpty(), nn(nkFormalParams, ident"string", nn(nkIdentDefs, ident"x", unembeddableType, newEmpty())), newEmpty(), newEmpty(), nn(nkStmtList, nn(nkCall, ident"$", nn(nkCall, ident"toPreserve", ident"x")))) procs.add nn(nkProcDef, "encode".ident.toExport, newEmpty(), newEmpty(), nn(nkFormalParams, nn(nkBracketExpr, ident"seq", ident"byte"), nn(nkIdentDefs, ident"x", unembeddableType, newEmpty())), newEmpty(), newEmpty(), nn(nkStmtList, nn(nkCall, ident"encode", nn(nkCall, ident"toPreserve", 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, {renderNone, renderIr}) 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/[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 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, fileExt) = splitFile inputPath let raw = readFile inputPath if raw[0] == 0xb4.char: var pr = decodePreserves raw if not fromPreserve(bundle, pr): var schema: Schema if fromPreserve(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)