preserves-nim/src/preserves/preserves_schema_nim.nim

952 lines
32 KiB
Nim
Raw Normal View History

# SPDX-FileCopyrightText: ☭ Emery Hemingway
2021-09-27 13:58:36 +00:00
# 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
2021-09-27 13:58:36 +00:00
import std/[hashes, strutils, sets, tables]
2021-09-27 13:58:36 +00:00
import compiler/[ast, idents, renderer, lineinfos]
import ../preserves, ./schema
2021-09-27 13:58:36 +00:00
type
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]
StringSet = HashSet[string]
proc schema(loc: Location): Schema = loc.bundle.modules[loc.schemaPath]
2021-09-27 13:58:36 +00:00
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 =
2021-10-17 12:50:27 +00:00
raiseAssert "need ident from " #& $sp
2021-09-27 13:58:36 +00:00
proc ident(cp: CompoundPattern; fallback: string): PNode =
case cp.orKind
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.rec:
2021-09-27 13:58:36 +00:00
ident($cp.rec.label)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tuple,
CompoundPatternKind.tuplePrefix,
CompoundPatternKind.dict:
2021-09-27 13:58:36 +00:00
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
2023-03-01 17:03:22 +00:00
of NamedPatternKind.named:
ident(string np.named.name)
2023-03-01 17:03:22 +00:00
of NamedPatternKind.anonymous:
2021-09-27 13:58:36 +00:00
ident(fallback)
proc ident(np: NamedSimplePattern; fallback: string): PNode =
case np.orKind
2023-03-01 17:03:22 +00:00
of NamedSimplePatternKind.named:
ident(string np.named.name)
2023-03-01 17:03:22 +00:00
of NamedSimplePatternKind.anonymous:
2021-09-27 13:58:36 +00:00
ident(fallback)
proc isEmbedded(ts: TypeSpec): bool =
embedded in ts.attrs
2023-06-07 10:45:00 +00:00
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
2021-09-27 13:58:36 +00:00
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 =
2023-03-01 17:03:22 +00:00
for m in `ref`.module: dotExtend(result, string m)
2023-06-07 10:45:00 +00:00
if `ref`.isAtomic:
dotExtend(result, `ref`.name.string)
else:
dotExtend(result, `ref`.name.string.capitalizeAscii)
2021-09-27 13:58:36 +00:00
proc deref(loc: Location; r: Ref): (Location, Definition) =
result[0] = loc
if r.module == @[]:
2023-05-16 22:06:52 +00:00
result[1] = loc.bundle.modules[loc.schemaPath].field0.definitions[r.name]
else:
result[0].schemaPath = r.module
2023-05-16 22:06:52 +00:00
result[1] = loc.bundle.modules[r.module].field0.definitions[r.name]
proc hasEmbeddedType(scm: Schema): bool =
2023-05-16 22:06:52 +00:00
case scm.field0.embeddedType.orKind
2023-03-01 17:03:22 +00:00
of EmbeddedtypenameKind.false: false
of EmbeddedtypenameKind.Ref: true
2021-09-27 13:58:36 +00:00
proc embeddedIdentString(scm: Schema): string =
2023-05-16 22:06:52 +00:00
case scm.field0.embeddedType.orKind
2023-03-01 17:03:22 +00:00
of EmbeddedtypenameKind.false:
"E"
2023-03-01 17:03:22 +00:00
of EmbeddedtypenameKind.Ref:
2023-05-16 22:06:52 +00:00
doAssert $scm.field0.embeddedType.ref.name != ""
$scm.field0.embeddedType.ref.name
2021-09-27 13:58:36 +00:00
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)
2021-09-27 13:58:36 +00:00
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 attrs(loc: Location; sp: SimplePattern; seen: RefSet): Attributes =
2021-10-17 12:50:27 +00:00
case sp.orKind
2023-03-01 17:03:22 +00:00
of SimplepatternKind.atom, SimplepatternKind.lit: {}
of SimplepatternKind.any, SimplepatternKind.embedded:
if loc.schema.hasEmbeddedType: {embedded}
else: {}
2023-03-01 17:03:22 +00:00
of SimplepatternKind.seqof:
attrs(loc, sp.seqof.pattern, seen)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.setof:
attrs(loc, sp.setof.pattern, seen)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.dictof:
attrs(loc, sp.dictof.key, seen) + attrs(loc, sp.dictof.value, seen)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.Ref:
if sp.ref in seen: {recursive}
2023-06-07 10:45:00 +00:00
elif sp.ref.isAtomic: {}
2021-10-17 12:50:27 +00:00
else:
var
(loc, def) = deref(loc, sp.ref)
seen = seen
incl(seen, sp.ref)
attrs(loc, def, seen)
2021-09-27 13:58:36 +00:00
proc attrs(loc: Location; np: NamedSimplePattern; seen: RefSet): Attributes =
2021-09-27 13:58:36 +00:00
case np.orKind
2023-03-01 17:03:22 +00:00
of NamedSimplePatternKind.named:
attrs(loc, np.named.pattern, seen)
2023-03-01 17:03:22 +00:00
of NamedSimplePatternKind.anonymous:
attrs(loc, np.anonymous, seen)
2021-09-27 13:58:36 +00:00
proc attrs(loc: Location; cp: CompoundPattern; seen: RefSet): Attributes =
2021-10-17 12:50:27 +00:00
case cp.orKind
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.rec:
result =
attrs(loc, cp.rec.label.pattern, seen) +
attrs(loc, cp.rec.fields.pattern, seen)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tuple:
for np in cp.tuple.patterns:
result = result + attrs(loc, np.pattern, seen)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tupleprefix:
result = attrs(loc, cp.tupleprefix.variable, seen)
for p in cp.tupleprefix.fixed:
result = result + attrs(loc, p, seen)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.dict:
for nsp in cp.dict.entries.values:
result = result + attrs(loc, nsp, seen)
2021-09-27 13:58:36 +00:00
proc attrs(loc: Location; pat: Pattern; seen: RefSet): Attributes =
2021-09-27 13:58:36 +00:00
case pat.orKind
2023-03-01 17:03:22 +00:00
of PatternKind.SimplePattern:
attrs(loc, pat.simplePattern, seen)
2023-03-01 17:03:22 +00:00
of PatternKind.CompoundPattern:
attrs(loc, pat.compoundPattern, seen)
2021-09-27 13:58:36 +00:00
proc attrs(loc: Location; orDef: DefinitionOr; seen: RefSet): Attributes =
2023-05-16 22:06:52 +00:00
result = attrs(loc, orDef.field0.pattern0, seen) + attrs(loc, orDef.field0.pattern1, seen)
for p in orDef.field0.patternN:
result = result + attrs(loc, p, seen)
2021-10-17 12:50:27 +00:00
proc attrs(loc: Location; def: Definition; seen: RefSet): Attributes =
2021-10-17 12:50:27 +00:00
case def.orKind
2023-03-01 17:03:22 +00:00
of DefinitionKind.or: result = attrs(loc, def.or, seen)
of DefinitionKind.and:
result =
2023-05-16 22:06:52 +00:00
attrs(loc, def.and.field0.pattern0, seen) +
attrs(loc, def.and.field0.pattern1, seen)
for p in def.and.field0.patternN:
result = result + attrs(loc, p, seen)
2023-03-01 17:03:22 +00:00
of DefinitionKind.Pattern:
result = attrs(loc, def.pattern, seen)
2021-10-17 12:50:27 +00:00
proc attrs(loc: Location; p: Definition|DefinitionOr|Pattern|CompoundPattern|SimplePattern): Attributes =
2021-09-27 13:58:36 +00:00
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)
2021-09-27 13:58:36 +00:00
proc isLiteral(loc: Location; def: Definition): bool {.gcsafe.}
proc isLiteral(loc: Location; pat: Pattern): bool {.gcsafe.}
2021-09-27 13:58:36 +00:00
proc isLiteral(loc: Location; sp: SimplePattern): bool =
2021-09-27 13:58:36 +00:00
case sp.orKind
of SimplepatternKind.Ref:
2023-06-07 10:45:00 +00:00
if sp.ref.module.len == 0 and not sp.ref.isAtomic:
var (loc, def) = deref(loc, sp.ref)
result = isLiteral(loc, def)
2021-09-27 13:58:36 +00:00
of SimplepatternKind.lit:
result = true
of SimplepatternKind.embedded:
result = isLiteral(loc, sp.embedded.interface)
2021-09-27 13:58:36 +00:00
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 =
2021-09-27 13:58:36 +00:00
case pat.orKind
of PatternKind.SimplePattern:
isLiteral(loc, pat.simplePattern)
2021-09-27 13:58:36 +00:00
of PatternKind.CompoundPattern:
false # TODO it could be a compound of all literals
proc isLiteral(loc: Location; def: Definition): bool =
2021-09-27 13:58:36 +00:00
if def.orKind == DefinitionKind.Pattern:
result = isLiteral(loc, def.pattern)
2021-09-27 13:58:36 +00:00
proc isRef(sp: SimplePattern): bool =
2023-03-01 17:03:22 +00:00
sp.orKind == SimplePatternKind.Ref
2021-09-27 13:58:36 +00:00
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 =
2023-05-16 22:06:52 +00:00
result = isLiteral(loc, orDef.field0.pattern0) and isLiteral(loc, orDef.field0.pattern1)
for na in orDef.field0.patternN:
2021-09-27 13:58:36 +00:00
if not result: break
result = isLiteral(loc, na)
2021-09-27 13:58:36 +00:00
proc isSymbolEnum(loc: Location; def: Definition): bool =
2021-09-27 13:58:36 +00:00
case def.orKind
of DefinitionKind.Pattern:
if def.pattern.orKind == PatternKind.SimplePattern and
2023-03-01 17:03:22 +00:00
def.pattern.simplePattern.orKind == SimplepatternKind.Ref:
var (loc, def) = deref(loc, def.pattern.simplePattern.ref)
result = isSymbolEnum(loc, def)
2021-09-27 13:58:36 +00:00
of DefinitionKind.or:
result = isSymbolEnum(loc, def.or)
2021-09-27 13:58:36 +00:00
else: discard
proc isSymbolEnum(loc: Location; sp: SimplePattern): bool =
# HashSet
2023-03-01 17:03:22 +00:00
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 =
case def.orKind
of DefinitionKind.Pattern:
case def.pattern.orKind
of PatternKind.SimplePattern:
case def.pattern.simplePattern.orKind
of SimplePatternKind.Ref:
2023-03-01 17:03:22 +00:00
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
of DefinitionKind.and:
# not actually "any" but it will be a Preserve[E] type
result = true
else: discard
2021-09-27 13:58:36 +00:00
proc typeIdent(atom: AtomKind): PNode =
case atom
2023-03-01 17:03:22 +00:00
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"
2021-09-27 13:58:36 +00:00
proc typeIdent(loc: Location; sp: SimplePattern): TypeSpec =
let scm = loc.schema
2021-09-27 13:58:36 +00:00
case sp.orKind
2023-03-01 17:03:22 +00:00
of SimplepatternKind.atom:
result = TypeSpec(node: typeIdent(sp.atom.atomKind))
2023-03-01 17:03:22 +00:00
of SimplepatternKind.seqof:
result = typeIdent(loc, sp.seqof.pattern)
2021-09-27 13:58:36 +00:00
result.node = nn(nkBracketExpr, ident"seq", result.node)
2023-03-01 17:03:22 +00:00
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)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.dictof:
2021-09-27 13:58:36 +00:00
let
key = typeIdent(loc, sp.dictof.key)
val = typeIdent(loc, sp.dictof.value)
2021-09-27 13:58:36 +00:00
result.node = nn(nkBracketExpr, ident"Table", key.node, val.node)
result.attrs = key.attrs + val.attrs
2023-03-01 17:03:22 +00:00
of SimplepatternKind.Ref:
result = TypeSpec(node: ident(sp.ref), attrs: attrs(loc, sp))
result.node = parameterize(scm, result)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.embedded:
2023-05-16 22:06:52 +00:00
case scm.field0.embeddedType.orKind
2023-03-01 17:03:22 +00:00
of EmbeddedtypenameKind.false:
result = typeIdent(loc, sp.embedded.interface)
2023-03-01 17:03:22 +00:00
of EmbeddedtypenameKind.Ref:
result = TypeSpec(node: scm.embeddedIdent())
incl(result.attrs, embedded)
2023-03-01 17:03:22 +00:00
of SimplepatternKind.any, SimplepatternKind.lit:
result = TypeSpec(node: preserveIdent(scm))
proc typeIdent(loc: Location; pat: Pattern): TypeSpec =
2021-09-27 13:58:36 +00:00
case pat.orKind
of PatternKind.SimplePattern: typeIdent(loc, pat.simplePattern)
2021-09-27 13:58:36 +00:00
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 =
2021-09-27 13:58:36 +00:00
case sp.orKind
of SimplePatternKind.lit:
2021-09-27 13:58:36 +00:00
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
2021-09-27 13:58:36 +00:00
proc toFieldIdent(s: string): PNode =
nn(nkPostFix, ident("*"), nn(nkAccQuoted, ident(s)))
proc toFieldIdent(loc: Location, label: string; pat: Pattern): PNode =
2021-09-27 13:58:36 +00:00
result = label.toFieldIdent
if isLiteral(loc, pat):
2021-09-27 13:58:36 +00:00
result = nn(nkPragmaExpr,
result,
nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesLiteral",
toStrLit(loc, pat.simplePattern))))
2021-09-27 13:58:36 +00:00
proc newEmpty(): PNode = newNode(nkEmpty)
proc embeddingParams(scm: Schema; embeddable: bool): PNode =
2021-09-27 13:58:36 +00:00
if embeddable:
nn(nkGenericParams, nn(nkIdentDefs, embeddedIdent(scm), newEmpty(), newEmpty()))
2021-09-27 13:58:36 +00:00
else:
newEmpty()
proc identDef(scm: Schema; a, b: PNode; embeddable: bool): PNode =
if embeddable and scm.hasEmbeddedType 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())
2021-09-27 13:58:36 +00:00
else:
nn(nkIdentDefs, a, b, newEmpty())
proc identDef(scm: Schema; l: PNode; ts: TypeSpec): PNode =
identDef(scm, l, ts.node, ts.isEmbedded)
2021-09-27 13:58:36 +00:00
proc label(pat: Pattern): string =
raiseAssert "need to derive record label for " & $pat
proc label(na: NamedPattern; index: int): string =
2021-09-27 13:58:36 +00:00
case na.orKind
2023-03-01 17:03:22 +00:00
of NamedPatternKind.named:
string na.named.name
2023-03-01 17:03:22 +00:00
of NamedPatternKind.anonymous:
"field" & $index
2021-09-27 13:58:36 +00:00
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
2021-09-27 13:58:36 +00:00
else: discard
2023-03-01 17:03:22 +00:00
doAssert(result != "", "no idStr for " & $sp)
2021-09-27 13:58:36 +00:00
proc idStr(pat: Pattern): string =
doAssert(pat.orKind == PatternKind.SimplePattern)
pat.simplePattern.idStr
proc idStr(np: NamedPattern): string =
case np.orKind
2023-03-01 17:03:22 +00:00
of NamedPatternKind.named:
string np.named.name
2023-03-01 17:03:22 +00:00
of NamedPatternKind.anonymous:
2021-09-27 13:58:36 +00:00
np.anonymous.idStr
proc typeDef(loc: Location; name: string; pat: Pattern; ty: PNode): PNode =
2021-09-27 13:58:36 +00:00
let
scm = loc.schema
embedParams = embeddingParams(scm, isEmbedded(loc, pat))
2021-09-27 13:58:36 +00:00
id = name.ident.toExport
case pat.orKind
2023-03-01 17:03:22 +00:00
of PatternKind.CompoundPattern:
2021-09-27 13:58:36 +00:00
case pat.compoundPattern.orKind
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.rec:
if isLiteral(loc, pat.compoundPattern.rec.label):
nn(nkTypeDef,
nn(nkPragmaExpr,
id,
nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesRecord",
PNode(kind: nkStrLit, strVal: pat.compoundPattern.rec.label.idStr)))),
embedParams,
ty)
else:
nn(nkTypeDef, name.ident.toExport, embedParams, ty)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tuple, CompoundPatternKind.tuplePrefix:
2021-09-27 13:58:36 +00:00
nn(nkTypeDef,
nn(nkPragmaExpr, id, nn(nkPragma, ident"preservesTuple")),
embedParams,
ty)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.dict:
2021-09-27 13:58:36 +00:00
nn(nkTypeDef,
2023-03-01 17:03:22 +00:00
nn(nkPragmaExpr,
id, nn(nkPragma, ident"preservesDictionary")),
2021-09-27 13:58:36 +00:00
embedParams,
ty)
else:
nn(nkTypeDef, name.ident.toExport, embedParams, ty)
proc typeDef(loc: Location; name: string; def: Definition; ty: PNode): PNode =
2021-09-27 13:58:36 +00:00
case def.orKind
of DefinitionKind.or:
let pragma = nn(nkPragma, ident"preservesOr")
if isSymbolEnum(loc, def):
pragma.add ident"pure"
2021-09-27 13:58:36 +00:00
nn(nkTypeDef,
nn(nkPragmaExpr,
name.ident.accQuote.toExport,
pragma),
embeddingParams(loc.schema, isEmbedded(loc, def)),
2021-09-27 13:58:36 +00:00
ty)
of DefinitionKind.and:
nn(nkTypeDef,
name.ident.toExport,
embeddingParams(loc.schema, isEmbedded(loc, def)),
preserveIdent(loc.schema))
2021-09-27 13:58:36 +00:00
of DefinitionKind.Pattern:
typeDef(loc, name, def.pattern, ty)
2021-09-27 13:58:36 +00:00
proc nimTypeOf(loc: Location; known: var TypeTable; nsp: NamedSimplePattern; name = ""): TypeSpec
proc nimTypeOf(loc: Location; known: var TypeTable; pat: Pattern; name = ""): TypeSpec
2021-09-27 13:58:36 +00:00
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)
2021-09-27 13:58:36 +00:00
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):
2021-09-27 13:58:36 +00:00
let id = nn(nkPragmaExpr,
id,
nn(nkPragma,
nn(nkExprColonExpr,
ident"preservesLiteral",
toStrLit(loc, sp))))
recList.add identDef(scm, id, TypeSpec(node: ident"tuple[]"))
elif sp.orKind == SimplePatternKind.embedded and not scm.hasEmbeddedType:
2023-03-01 17:03:22 +00:00
let id = nn(nkPragmaExpr,
id, nn(nkPragma, ident"preservesEmbedded"))
recList.add identDef(scm, id, nimTypeOf(loc, known, sp))
2021-09-27 13:58:36 +00:00
else:
recList.add identDef(scm, id, nimTypeOf(loc, known, sp))
2021-09-27 13:58:36 +00:00
proc addFields(recList: PNode; loc: Location; known: var TypeTable; cp: CompoundPattern; parentName: string): PNode {.discardable.} =
let scm = loc.schema
template addField(np: NamedPattern; index: int) =
2021-10-24 12:00:18 +00:00
let
label = label(np, index)
2021-10-24 12:00:18 +00:00
id = label.toFieldIdent
pattern = np.pattern
if pattern.isRef or pattern.isSimple:
addField(recList, loc, known, pattern.simplePattern, label)
2021-10-24 12:00:18 +00:00
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))
2021-09-27 13:58:36 +00:00
case cp.orKind
of CompoundPatternKind.rec:
# recList.add identDef(scm, ident(label), nimTypeOf(loc, known, cp, ""))
2021-10-17 12:50:27 +00:00
raiseassert "unexpected record of fields " #& $cp.rec
2021-09-27 13:58:36 +00:00
of CompoundPatternKind.tuple:
for i, np in cp.tuple.patterns: addField(np, i)
2021-10-24 12:00:18 +00:00
of CompoundPatternKind.tuplePrefix:
for i, np in cp.tuplePrefix.fixed: addField(np, i)
let variableType = nimTypeOf(loc, known, cp.tuplePrefix.variable)
2021-10-24 12:00:18 +00:00
recList.add identDef(
scm,
2021-10-24 12:00:18 +00:00
nn(nkPragmaExpr,
ident(cp.tuplePrefix.variable, parentName).accQuote.toExport,
nn(nkPragma, ident"preservesTupleTail")),
parameterize(scm, variableType),
variableType.isEmbedded)
2021-10-17 12:50:27 +00:00
else: raiseAssert "not adding fields for " #& $cp
2021-09-27 13:58:36 +00:00
reclist
proc addFields(recList: PNode; loc: Location; known: var TypeTable; pat: Pattern; parentName: string): PNode {.discardable.} =
2021-09-27 13:58:36 +00:00
case pat.orKind
of PatternKind.SimplePattern:
addField(recList, loc, known, pat.simplePattern, "field0")
2021-09-27 13:58:36 +00:00
of PatternKind.CompoundPattern:
discard addFields(recList, loc, known, pat.compoundPattern, parentName)
2021-09-27 13:58:36 +00:00
reclist
proc addFields(recList: PNode; loc: Location; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} =
2021-09-27 13:58:36 +00:00
for key, val in entries.pairs:
doAssert(key.isSymbol)
let label = string key.symbol
addField(recList, loc, known, val.pattern, label)
2021-09-27 13:58:36 +00:00
recList
proc nimTypeOf(loc: Location; known: var TypeTable; nsp: NamedSimplePattern; name: string): TypeSpec =
2021-09-27 13:58:36 +00:00
case nsp.orKind
of NamedsimplepatternKind.named:
nimTypeOf(loc, known, nsp.named.pattern, string nsp.named.name)
2021-09-27 13:58:36 +00:00
of NamedsimplepatternKind.anonymous:
nimTypeOf(loc, known, nsp.anonymous, name)
2021-09-27 13:58:36 +00:00
proc nimTypeOf(loc: Location; known: var TypeTable; rec: CompoundPatternRec; name: string): TypeSpec =
if isLiteral(loc, rec.label):
result.node = nn(nkObjectTy,
newEmpty(), newEmpty(),
nn(nkRecList).addFields(loc, known, rec.fields.pattern, name))
else:
result.node = preserveIdent(loc.schema)
proc nimTypeOf(loc: Location; known: var TypeTable; cp: CompoundPattern; name: string): TypeSpec =
2021-09-27 13:58:36 +00:00
case cp.orKind
of CompoundPatternKind.`rec`:
result = nimTypeOf(loc, known, cp.rec, name)
2021-10-24 12:00:18 +00:00
of CompoundPatternKind.`tuple`, CompoundPatternKind.`tupleprefix`:
result.node = nn(nkObjectTy,
newEmpty(), newEmpty(),
nn(nkRecList).addFields(loc, known, cp, name))
2021-09-27 13:58:36 +00:00
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):
2021-10-17 12:50:27 +00:00
result.node = nn(nkRefTy, result.node)
2021-09-27 13:58:36 +00:00
proc nimTypeOf(loc: Location; known: var TypeTable; pat: Pattern; name: string): TypeSpec =
2021-09-27 13:58:36 +00:00
case pat.orKind
of PatternKind.SimplePattern:
nimTypeOf(loc, known, pat.simplePattern, name)
2021-09-27 13:58:36 +00:00
of PatternKind.CompoundPattern:
nimTypeOf(loc, known, pat.compoundPattern, name)
2021-09-27 13:58:36 +00:00
proc nimTypeOf(loc: Location; known: var TypeTable; orDef: DefinitionOr; name: string): TypeSpec =
let scm = loc.schema
2021-09-27 13:58:36 +00:00
proc toEnumTy(): PNode =
let ty = nkEnumTy.newNode.add newEmpty()
proc add (na: NamedAlternative) =
ty.add na.variantLabel.ident.accQuote
2023-05-16 22:06:52 +00:00
add(orDef.field0.pattern0)
add(orDef.field0.pattern1)
for na in orDef.field0.patternN:
2021-09-27 13:58:36 +00:00
add(na)
ty
if isSymbolEnum(loc, orDef):
2021-09-27 13:58:36 +00:00
result.node = toEnumTy()
else:
let
enumName = name & "Kind"
enumPath = loc.schemaPath & @[Symbol enumName]
2021-09-27 13:58:36 +00:00
enumIdent = ident(enumName)
if enumPath notin known:
known[enumPath] = nn(nkTypeDef,
2021-09-27 13:58:36 +00:00
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):
2021-09-27 13:58:36 +00:00
memberType.node = ident"bool"
elif na.pattern.isRef:
memberType = typeIdent(loc, na.pattern)
2021-09-27 13:58:36 +00:00
else:
let
memberTypeName = name & na.variantLabel.capitalizeAscii
memberPath = loc.schemaPath & @[Symbol memberTypeName]
2021-09-27 13:58:36 +00:00
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)
2021-10-17 12:50:27 +00:00
memberType.node = parameterize(
scm, memberType.node, isEmbedded(loc, na.pattern))
2021-09-27 13:58:36 +00:00
branchRecList.add nn(nkIdentDefs,
toFieldIdent(loc, na.variantLabel.normalize, na.pattern),
2021-09-27 13:58:36 +00:00
memberType.node, newEmpty())
recCase.add nn(nkOfBranch,
nn(nkDotExpr,
enumIdent, na.variantLabel.ident.accQuote),
branchRecList)
2023-05-16 22:06:52 +00:00
addCase(orDef.field0.pattern0)
addCase(orDef.field0.pattern1)
for na in orDef.field0.patternN: addCase(na)
2021-10-17 12:50:27 +00:00
result.node = nn(nkObjectTy,
2021-09-27 13:58:36 +00:00
newEmpty(),
newEmpty(),
2021-10-17 12:50:27 +00:00
nn(nkRecList, recCase))
# result.attrs = attrs(loc, orDef)
if result.node.kind == nkObjectTy and (recursive in attrs(loc, orDef)):
2021-10-17 12:50:27 +00:00
result.node = nn(nkRefTy, result.node)
2021-09-27 13:58:36 +00:00
proc nimTypeOf(loc: Location; known: var TypeTable; def: Definition; name: string): TypeSpec =
2021-09-27 13:58:36 +00:00
case def.orKind
of DefinitionKind.or:
nimTypeOf(loc, known, def.or, name)
of DefinitionKind.and:
TypeSpec(node: preserveIdent(loc.schema))
2021-09-27 13:58:36 +00:00
of DefinitionKind.Pattern:
nimTypeOf(loc, known, def.pattern, name)
2021-09-27 13:58:36 +00:00
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)
2021-09-27 13:58:36 +00:00
proc collectRefImports(imports: var StringSet; loc: Location; sp: SimplePattern) =
2021-09-27 13:58:36 +00:00
case sp.orKind
of SimplePatternKind.setof:
incl(imports, "std/sets")
2021-09-27 13:58:36 +00:00
of SimplePatternKind.dictof:
incl(imports, "std/tables")
2021-09-27 13:58:36 +00:00
of SimplePatternKind.Ref:
2023-03-01 17:03:22 +00:00
if sp.ref.module != @[] and sp.ref.module != loc.schemaPath:
incl(imports, string sp.ref.module[0])
2021-09-27 13:58:36 +00:00
else: discard
proc collectRefImports(imports: var StringSet; loc: Location; cp: CompoundPattern) =
2021-09-27 13:58:36 +00:00
case cp.orKind
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.rec:
collectRefImports(imports, loc, cp.rec.label.pattern)
collectRefImports(imports, loc, cp.rec.fields.pattern)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tuple:
for p in cp.tuple.patterns: collectRefImports(imports, loc, p.pattern)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.tupleprefix:
for np in cp.tupleprefix.fixed: collectRefImports(imports, loc, np.pattern)
collectRefImports(imports, loc, cp.tupleprefix.variable.pattern)
2023-03-01 17:03:22 +00:00
of CompoundPatternKind.dict:
2021-09-27 13:58:36 +00:00
for nsp in cp.dict.entries.values:
collectRefImports(imports, loc, nsp.pattern)
2021-09-27 13:58:36 +00:00
proc collectRefImports(imports: var StringSet; loc: Location; pat: Pattern) =
2021-09-27 13:58:36 +00:00
case pat.orKind
of PatternKind.SimplePattern:
collectRefImports(imports, loc, pat.simplePattern)
2021-09-27 13:58:36 +00:00
of PatternKind.CompoundPattern:
collectRefImports(imports, loc, pat.compoundPattern)
2021-09-27 13:58:36 +00:00
proc collectRefImports(imports: var StringSet; loc: Location; def: Definition) =
2021-09-27 13:58:36 +00:00
case def.orKind
2023-03-01 17:03:22 +00:00
of DefinitionKind.or:
2023-05-16 22:06:52 +00:00
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)
2023-03-01 17:03:22 +00:00
of DefinitionKind.and:
2023-05-16 22:06:52 +00:00
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)
2021-09-27 13:58:36 +00:00
of DefinitionKind.Pattern:
collectRefImports(imports, loc, def.pattern)
2021-09-27 13:58:36 +00:00
proc collectRefImports(imports: var StringSet; loc: Location; scm: Schema) =
2023-05-16 22:06:52 +00:00
for _, def in scm.field0.definitions:
collectRefImports(imports, loc, def)
2021-09-27 13:58:36 +00:00
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:
let loc = (bundle, scmPath)
var
typeSection = newNode nkTypeSection
procs: seq[PNode]
unembeddableType, embeddableType: PNode
2023-05-16 22:06:52 +00:00
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(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)
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 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})
2021-09-27 13:58:36 +00:00
when isMainModule:
2021-11-11 10:11:40 +00:00
import ./schemaparse
proc writeModules(bundle: Bundle) =
let modules = renderNimBundle(bundle)
for path, txt in modules.pairs:
writeFile(path, txt)
stdout.writeLine(path)
2021-09-27 13:58:36 +00:00
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:
new bundle
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:
2023-04-01 17:33:15 +00:00
var (dirPath, fileName, _) = 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:
new bundle
var scm = parsePreservesSchema(readFile(inputPath), dirPath)
bundle.modules[@[Symbol fileName]] = scm
if bundle.isNil or bundle.modules.len == 0:
quit "Failed to recognize " & inputPath
2021-11-11 10:11:40 +00:00
else:
writeModules(bundle)