470 lines
14 KiB
Nim
470 lines
14 KiB
Nim
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
import std/[os, strutils, tables, times]
|
|
|
|
import compiler/[ast, idents, renderer, lineinfos]
|
|
|
|
import ../../preserves, ../schemas
|
|
|
|
type TypeTable = OrderedTable[string, PNode]
|
|
|
|
proc add(parent, child: PNode): PNode {.discardable.} =
|
|
parent.sons.add child
|
|
parent
|
|
|
|
proc add(parent: PNode; children: varargs[PNode]): PNode {.discardable.} =
|
|
parent.sons.add children
|
|
parent
|
|
|
|
proc child(sn: SchemaNode): SchemaNode =
|
|
assert(sn.nodes.len == 1)
|
|
sn.nodes[0]
|
|
|
|
proc nn(kind: TNodeKind; children: varargs[PNode]): PNode =
|
|
result = newNode(kind)
|
|
result.sons.add(children)
|
|
|
|
proc nn(kind: TNodeKind; child: PNode): PNode =
|
|
result = newNode(kind)
|
|
result.sons.add(child)
|
|
|
|
proc ident(s: string): PNode =
|
|
newIdentNode(PIdent(s: s), TLineInfo())
|
|
|
|
proc accQuote(n: PNode): Pnode =
|
|
nkAccQuoted.newNode.add(n)
|
|
|
|
proc ident(sn: SchemaNode): PNode =
|
|
var s: string
|
|
case sn.kind
|
|
of snkAlt:
|
|
s = sn.altLabel.toLower.nimIdentNormalize
|
|
of snkLiteral: s = $sn.value
|
|
of snkRecord:
|
|
s = $sn.nodes[0]
|
|
of snkNamed:
|
|
s = sn.name
|
|
of snkDictionary, snkVariableTuple:
|
|
s = "data"
|
|
else:
|
|
raiseAssert("no ident for " & $sn.kind & " " & $sn)
|
|
ident(s)
|
|
|
|
proc typeIdent(sn: SchemaNode): PNode =
|
|
case sn.kind
|
|
of snkAtom:
|
|
case sn.atom
|
|
of akBool: ident"bool"
|
|
of akFloat: ident"float32"
|
|
of akDouble: ident"float64"
|
|
of akInt: ident"BiggestInt"
|
|
of akString: ident"string"
|
|
of akBytes: nn(nkBracketExpr, ident"seq", ident"byte")
|
|
of akSymbol: ident"string" # TODO: distinct string type for symbols?
|
|
of snkNamed:
|
|
sn.pattern.typeIdent
|
|
of snkRef:
|
|
ident($sn)
|
|
else:
|
|
stderr.writeLine("no typeIdent for " & $sn.kind & " " & $sn)
|
|
ident"Preserve"
|
|
|
|
proc toExport(n: sink PNode): PNode =
|
|
nkPostFix.newNode.add(ident"*", n)
|
|
|
|
proc newEmpty(): PNode = newNode(nkEmpty)
|
|
|
|
proc isConst(sn: SchemaNode): bool =
|
|
case sn.kind
|
|
of snkLiteral: true
|
|
else: false
|
|
|
|
proc isSymbolEnum(sn: SchemaNode): bool =
|
|
if sn.kind == snkOr:
|
|
for bn in sn.nodes:
|
|
if bn.altBranch.kind != snkLiteral or
|
|
bn.altBranch.value.kind != pkSymbol:
|
|
return false
|
|
result = true
|
|
|
|
proc toEnumTy(sn: SchemaNode): PNode =
|
|
result = nkEnumTy.newNode.add newEmpty()
|
|
for bn in sn.nodes:
|
|
result.add bn.altLabel.nimIdentNormalize.ident.accQuote
|
|
|
|
proc toEnumDef(name: string; sn: SchemaNode): PNode =
|
|
nkTypeDef.newNode.add(
|
|
nkPragmaExpr.newNode.add(
|
|
name.ident.toExport,
|
|
nkPragma.newNode.add(ident"pure")),
|
|
newEmpty(),
|
|
sn.toEnumTy)
|
|
|
|
proc nimTypeOf(known: var TypeTable; sn: SchemaNode; name = ""): PNode =
|
|
case sn.kind
|
|
of snkOr:
|
|
if sn.isSymbolEnum:
|
|
result = sn.toEnumTy
|
|
else:
|
|
let
|
|
enumName = name.nimIdentNormalize & "Kind"
|
|
enumIdent = nn(nkPostFix, ident"*", ident(enumName))
|
|
if enumName notin known:
|
|
known[enumName] = toEnumDef(enumName, sn)
|
|
let recCase = nkRecCase.newNode.add(
|
|
nkIdentDefs.newNode.add(
|
|
"kind".ident.toExport,
|
|
enumName.ident,
|
|
newEmpty()))
|
|
for bn in sn.nodes:
|
|
assert(bn.kind == snkAlt, $bn.kind)
|
|
var recList = nkRecList.newNode
|
|
case bn.altBranch.kind
|
|
of snkRecord:
|
|
case bn.altBranch.nodes.len
|
|
of 0, 1: discard
|
|
of 2:
|
|
let label = bn.ident
|
|
recList.add nkIdentDefs.newNode.add(
|
|
label.accQuote.toExport,
|
|
nimTypeOf(known, bn.altBranch.nodes[1], $label),
|
|
newEmpty())
|
|
else:
|
|
for i, field in bn.altBranch.nodes:
|
|
if i > 0:
|
|
let label = field.ident
|
|
recList.add nkIdentDefs.newNode.add(
|
|
label.accQuote.toExport,
|
|
nimTypeOf(known, field, $label),
|
|
newEmpty())
|
|
else:
|
|
if bn.altBranch.isConst:
|
|
recList.add nkDiscardStmt.newNode.add(newEmpty())
|
|
else:
|
|
let label = bn.ident
|
|
recList.add(nkIdentDefs.newNode.add(
|
|
label.accQuote.toExport,
|
|
nimTypeOf(known, bn.altBranch, $label),
|
|
newEmpty()))
|
|
let disc = nkDotExpr.newNode.add(
|
|
enumIdent, bn.altLabel.nimIdentNormalize.ident.accQuote)
|
|
recCase.add nkOfBranch.newNode.add(disc, recList)
|
|
result = nn(nkObjectTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
nn(nkRecList, recCase))
|
|
of snkAny:
|
|
result = ident"Preserve"
|
|
of snkAtom:
|
|
result = typeIdent(sn)
|
|
of snkEmbedded:
|
|
result = nimTypeOf(known, sn.embed)
|
|
of snkLiteral:
|
|
result = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim
|
|
of pkBoolean: ident"bool"
|
|
of pkFloat: ident"float32"
|
|
of pkDouble: ident"float64"
|
|
of pkSignedInteger: ident"BiggestInt"
|
|
of pkBigInteger: ident"BigInt"
|
|
of pkString: ident"string"
|
|
of pkByteString: nn(
|
|
nkBracketExpr, ident"seq", ident"byte")
|
|
of pkSymbol: ident"string"
|
|
of pkRecord: ident"Preserve"
|
|
of pkSequence: nn(
|
|
nkBracketExpr, ident"seq", ident"Preserve")
|
|
of pkSet: nn(
|
|
nkBracketExpr, ident"HashSet", ident"Preserve")
|
|
of pkDictionary: nn(
|
|
nkBracketExpr, ident"Table", ident"Preserve", ident"Preserve")
|
|
of pkEmbedded: nn(nkDiscardStmt, newEmpty()) # TODO: is this possible?
|
|
of snkSequenceOf:
|
|
result = nkBracketExpr.newNode.add(
|
|
ident"seq",
|
|
nimTypeOf(known, sn.child))
|
|
of snkSetOf:
|
|
result = nkBracketExpr.newNode.add(
|
|
ident"HashedSet",
|
|
nimTypeOf(known, sn.child))
|
|
of snkDictOf:
|
|
result = nkBracketExpr.newNode.add(
|
|
ident"Table",
|
|
nimTypeOf(known, sn.nodes[0]),
|
|
nimTypeOf(known, sn.nodes[1]))
|
|
of snkRecord:
|
|
case sn.nodes.len
|
|
of 0, 1:
|
|
result = nn(nkObjectTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
nn(nkDiscardStmt, newEmpty()))
|
|
else:
|
|
let recList = nkRecList.newNode()
|
|
for i, field in sn.nodes:
|
|
if i > 0:
|
|
let id = field.ident
|
|
recList.add nkIdentDefs.newNode.add(
|
|
id.accQuote.toExport,
|
|
nimTypeOf(known, field, $id),
|
|
newEmpty())
|
|
result = nn(nkObjectTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
recList)
|
|
of snkTuple, snkVariableTuple:
|
|
# TODO: the variable part
|
|
result = nkTupleTy.newNode
|
|
for tn in sn.nodes:
|
|
result.add nkIdentDefs.newNode.add(
|
|
tn.ident.accQuote, nimTypeOf(known, tn), newEmpty())
|
|
of snkDictionary:
|
|
result = nkTupleTy.newNode
|
|
for i in countup(0, sn.nodes.high, 2):
|
|
let id = ident(sn.nodes[i+0])
|
|
result.add nkIdentDefs.newNode.add(
|
|
id.accQuote,
|
|
nimTypeOf(known, sn.nodes[i+1], $id),
|
|
newEmpty())
|
|
of snkNamed:
|
|
result = nimTypeOf(known, sn.pattern, name)
|
|
of snkRef:
|
|
result = ident $sn
|
|
else:
|
|
result = nkCommentStmt.newNode
|
|
result.comment.add("Missing type generator for " & $sn.kind & " " & $sn)
|
|
|
|
proc toConst(name: string; def: SchemaNode): Pnode =
|
|
case def.kind
|
|
of snkLiteral:
|
|
result = nkConstDef.newNode.add(
|
|
name.ident.accQuote, newEmpty())
|
|
case def.value.kind
|
|
of pkSignedInteger:
|
|
discard result.add newIntNode(nkIntLit, def.value.int)
|
|
of pkSymbol:
|
|
discard result.add nn(nkCall,
|
|
ident"symbol",
|
|
PNode(kind: nkStrLit, strVal: def.value.symbol),
|
|
ident"EmbeddedType")
|
|
else:
|
|
raiseAssert("cannot convert " & $def & " to a Nim literal")
|
|
else: discard
|
|
|
|
proc toNimLit(sn: SchemaNode): PNode =
|
|
assert(sn.kind == snkLiteral, $sn)
|
|
case sn.value.kind
|
|
of pkSymbol:
|
|
nkCall.newNode.add(
|
|
ident"symbol",
|
|
PNode(kind: nkStrLit, strVal: sn.value.symbol),
|
|
ident"EmbeddedType")
|
|
else:
|
|
raiseAssert("no Nim literal for " & $sn)
|
|
|
|
proc preserveTypeOf(known: var TypeTable; sn: SchemaNode; name = ""): PNode =
|
|
case sn.kind
|
|
of snkOr:
|
|
if sn.isSymbolEnum:
|
|
result = sn.toEnumTy
|
|
else:
|
|
let enumName = name.nimIdentNormalize & "Kind"
|
|
if enumName notin known:
|
|
known[enumName] = toEnumDef(enumName, sn)
|
|
result = nkObjectTy.newNode.add(
|
|
newEmpty(),
|
|
newEmpty(),
|
|
nkRecList.newNode.add(
|
|
nkIdentDefs.newNode.add(
|
|
ident"value", ident"Preserve", newEmpty()),
|
|
nkIdentDefs.newNode.add(
|
|
"schemaKind".ident,
|
|
enumName.ident,
|
|
newEmpty())))
|
|
of snkAny:
|
|
result = ident"Preserve"
|
|
of snkAtom:
|
|
result = case sn.atom
|
|
of akBool: ident"bool"
|
|
of akFloat: ident"float32"
|
|
of akDouble: ident"float64"
|
|
of akInt: ident"BiggestInt"
|
|
of akString: ident"string"
|
|
of akBytes: nkBracketExpr.newNode.add(
|
|
ident"seq",
|
|
ident"byte")
|
|
of akSymbol: ident"string" # TODO: distinct string type for symbols?
|
|
of snkRecord:
|
|
case sn.nodes.len
|
|
of 0, 1:
|
|
result = nn(nkObjectTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
nn(nkDiscardStmt, newEmpty()))
|
|
else:
|
|
let recList = nkRecList.newNode()
|
|
for i, field in sn.nodes:
|
|
if i > 0:
|
|
let id = field.ident
|
|
recList.add nkIdentDefs.newNode.add(
|
|
id.accQuote.toExport,
|
|
nimTypeOf(known, field, $id),
|
|
newEmpty())
|
|
result = nn(nkObjectTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
recList)
|
|
of snkTuple:
|
|
let recList = nkRecList.newNode()
|
|
for i, field in sn.nodes:
|
|
let id = field.ident
|
|
recList.add nkIdentDefs.newNode.add(
|
|
id.accQuote,
|
|
nimTypeOf(known, field, $id),
|
|
newEmpty())
|
|
result = nn(nkTupleTy,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
recList)
|
|
of snkNamed:
|
|
result = preserveTypeOf(known, sn.pattern, sn.name)
|
|
of snkRef:
|
|
result = ident $sn
|
|
else:
|
|
result = nn(nkDistinctTy, ident"Preserve")
|
|
result.comment = "``" & $sn & "``"
|
|
|
|
proc generateProcs(result: var seq[PNode]; name: string; sn: SchemaNode) =
|
|
proc exportIdent(id: string): PNode = nn(nkPostFix, ident"*", ident(id))
|
|
case sn.kind
|
|
of snkRecord:
|
|
var
|
|
params = nn(nkFormalParams, ident"Preserve")
|
|
initRecordCall = nn(nkCall,
|
|
nn(nkBracketExpr, ident"initRecord", ident"EmbeddedType"),
|
|
sn.nodes[0].toNimLit)
|
|
for i, field in sn.nodes:
|
|
if i > 0:
|
|
let id = field.ident
|
|
params.add nn(nkIdentDefs,
|
|
id,
|
|
nn(nkInfix,
|
|
ident"|",
|
|
ident"Preserve",
|
|
field.typeIdent),
|
|
newEmpty())
|
|
initRecordCall.add(
|
|
nn(nkCall, ident"toPreserve", id, ident"EmbeddedType"))
|
|
result.add nn(nkProcDef,
|
|
exportIdent("prs" & name),
|
|
newEmpty(),
|
|
newEmpty(),
|
|
params,
|
|
newEmpty(),
|
|
newEmpty(),
|
|
nn(nkStmtList, initRecordCall))
|
|
else: discard
|
|
|
|
proc generateNimFile*(scm: Schema; path: string) =
|
|
var
|
|
knownTypes: TypeTable
|
|
typeSection = newNode nkTypeSection
|
|
constSection = newNode nkConstSection
|
|
procs: seq[PNode]
|
|
if scm.embeddedType == "":
|
|
typeSection.add nn(nkTypeDef,
|
|
ident"EmbeddedType",
|
|
newEmpty(),
|
|
ident"void")
|
|
else:
|
|
typeSection.add nn(nkTypeDef,
|
|
ident"EmbeddedType",
|
|
newEmpty(),
|
|
ident(scm.embeddedType))
|
|
typeSection.add nn(nkTypeDef,
|
|
ident"Preserve",
|
|
newEmpty(),
|
|
nn(nkBracketExpr,
|
|
ident"PreserveGen",
|
|
ident(scm.embeddedType)))
|
|
for name, def in scm.definitions.pairs:
|
|
if def.isConst:
|
|
constSection.add toConst(name, def)
|
|
else:
|
|
let t = preserveTypeOf(knownTypes, def, name)
|
|
case def.kind
|
|
of snkAtom:
|
|
knownTypes[name] = nkTypeDef.newNode.add(
|
|
name.ident.toExport, newEmpty(), t)
|
|
else:
|
|
if def.kind == snkRecord:
|
|
knownTypes[name] = nn(nkTypeDef,
|
|
nn(nkPragmaExpr,
|
|
name.ident.toExport,
|
|
nn(nkPragma,
|
|
nn(nkExprColonExpr,
|
|
ident"record",
|
|
PNode(kind: nkStrLit, strVal: $def.nodes[0])))),
|
|
newEmpty(),
|
|
t)
|
|
else:
|
|
case t.kind
|
|
of nkEnumTy:
|
|
knownTypes[name] = nn(nkTypeDef,
|
|
nn(nkPragmaExpr,
|
|
name.ident.toExport,
|
|
nn(nkPragma, ident"pure")),
|
|
newEmpty(),
|
|
t)
|
|
of nkDistinctTy:
|
|
knownTypes[name] = nn(nkTypeDef,
|
|
nn(nkPragmaExpr,
|
|
name.ident.toExport,
|
|
nn(nkPragma,
|
|
nn(nkExprColonExpr,
|
|
ident"borrow",
|
|
accQuote(ident".")))),
|
|
newEmpty(),
|
|
t)
|
|
else:
|
|
knownTypes[name] = nn(nkTypeDef,
|
|
name.ident.toExport, newEmpty(), t)
|
|
generateProcs(procs, name, def)
|
|
for typeDef in knownTypes.values:
|
|
typeSection.add typeDef
|
|
var
|
|
dateComment = PNode(kind: nkCommentStmt, comment:
|
|
"Date of generation: " & now().format("yyyy-MM-dd HH:mm"))
|
|
imports = nkImportStmt.newNode.add(
|
|
ident"std/typetraits",
|
|
ident"preserves")
|
|
module = newNode(nkStmtList).add(
|
|
dateComment,
|
|
imports,
|
|
typeSection,
|
|
constSection
|
|
).add(procs)
|
|
echo path
|
|
writeFile(path, renderTree(module, {renderNone, renderIr}))
|
|
|
|
when isMainModule:
|
|
import std/parseopt
|
|
var inputs: seq[string]
|
|
for kind, key, val in getopt():
|
|
case kind
|
|
of cmdLongOption:
|
|
case key
|
|
else: quit("unhandled option " & key)
|
|
of cmdShortOption:
|
|
case key
|
|
else: quit("unhandled option " & key)
|
|
of cmdArgument:
|
|
inputs.add absolutePath(key)
|
|
of cmdEnd: discard
|
|
for filepath in inputs:
|
|
let
|
|
scm = parsePreservesSchema(readFile filepath, filepath)
|
|
(dir, name, _) = splitFile(filepath)
|
|
outputPath = dir / name & ".nim"
|
|
generateNimFile(scm, outputPath)
|