440 lines
13 KiB
Nim
440 lines
13 KiB
Nim
import
|
|
std/[macros, tables, hashes]
|
|
|
|
export
|
|
macros
|
|
|
|
type
|
|
FieldDescription* = object
|
|
name*: NimNode
|
|
isPublic*: bool
|
|
isDiscriminator*: bool
|
|
typ*: NimNode
|
|
pragmas*: NimNode
|
|
caseField*: NimNode
|
|
caseBranch*: NimNode
|
|
|
|
const
|
|
nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit}
|
|
|
|
proc hash*(x: LineInfo): Hash =
|
|
!$(hash(x.filename) !& hash(x.line) !& hash(x.column))
|
|
|
|
var
|
|
# Please note that we are storing NimNode here in order to
|
|
# incur the code rendering cost only on a successful compilation.
|
|
macroLocations {.compileTime.} = newSeq[LineInfo]()
|
|
macroOutputs {.compileTime.} = newSeq[NimNode]()
|
|
|
|
proc writeMacroResultsNow* {.compileTime.} =
|
|
var files = initTable[string, NimNode]()
|
|
|
|
proc addToFile(file: var NimNode, location: LineInfo, macroOutput: NimNode) =
|
|
if file == nil:
|
|
file = newNimNode(nnkStmtList, macroOutput)
|
|
|
|
file.add newCommentStmtNode("Generated at line " & $location.line)
|
|
file.add macroOutput
|
|
|
|
for i in 0..< macroLocations.len:
|
|
addToFile files.mgetOrPut(macroLocations[i].filename, nil),
|
|
macroLocations[i], macroOutputs[i]
|
|
|
|
for name, contents in files:
|
|
let targetFile = name & ".generated.nim"
|
|
writeFile(targetFile, repr(contents))
|
|
hint "Wrote macro output to " & targetFile, contents
|
|
|
|
proc storeMacroResult*(callSite: LineInfo,
|
|
macroResult: NimNode,
|
|
writeOutputImmediately = false) =
|
|
macroLocations.add callSite
|
|
macroOutputs.add macroResult
|
|
if writeOutputImmediately:
|
|
# echo macroResult.repr
|
|
writeMacroResultsNow()
|
|
|
|
proc storeMacroResult*(macroResult: NimNode, writeOutputImmediately = false) =
|
|
let usageSite = callsite().lineInfoObj
|
|
storeMacroResult(usageSite, macroResult, writeOutputImmediately)
|
|
|
|
macro dumpMacroResults*: untyped =
|
|
writeMacroResultsNow()
|
|
|
|
proc findPragma*(pragmas: NimNode, pragmaSym: NimNode): NimNode =
|
|
for p in pragmas:
|
|
if p.kind in {nnkSym, nnkIdent} and eqIdent(p, pragmaSym):
|
|
return p
|
|
if p.kind in nnkPragmaCallKinds and p.len > 0 and eqIdent(p[0], pragmaSym):
|
|
return p
|
|
|
|
func isTuple*(t: NimNode): bool =
|
|
t.kind == nnkBracketExpr and t[0].kind == nnkSym and eqIdent(t[0], "tuple")
|
|
|
|
macro isTuple*(T: type): untyped =
|
|
newLit(isTuple(getType(T)[1]))
|
|
|
|
proc skipRef*(T: NimNode): NimNode =
|
|
result = T
|
|
if T.kind == nnkBracketExpr:
|
|
if eqIdent(T[0], bindSym"ref"):
|
|
result = T[1]
|
|
|
|
template readPragma*(field: FieldDescription, pragmaName: static string): NimNode =
|
|
let p = findPragma(field.pragmas, bindSym(pragmaName))
|
|
if p != nil and p.len == 2: p[1] else: p
|
|
|
|
proc collectFieldsFromRecList(result: var seq[FieldDescription],
|
|
n: NimNode,
|
|
parentCaseField: NimNode = nil,
|
|
parentCaseBranch: NimNode = nil,
|
|
isDiscriminator = false) =
|
|
case n.kind
|
|
of nnkRecList:
|
|
for entry in n:
|
|
collectFieldsFromRecList result, entry,
|
|
parentCaseField, parentCaseBranch
|
|
of nnkRecWhen:
|
|
for branch in n:
|
|
case branch.kind:
|
|
of nnkElifBranch:
|
|
collectFieldsFromRecList result, branch[1],
|
|
parentCaseField, parentCaseBranch
|
|
of nnkElse:
|
|
collectFieldsFromRecList result, branch[0],
|
|
parentCaseField, parentCaseBranch
|
|
else:
|
|
doAssert false
|
|
|
|
of nnkRecCase:
|
|
collectFieldsFromRecList result, n[0],
|
|
parentCaseField,
|
|
parentCaseBranch,
|
|
isDiscriminator = true
|
|
|
|
for i in 1 ..< n.len:
|
|
let branch = n[i]
|
|
case branch.kind
|
|
of nnkOfBranch:
|
|
collectFieldsFromRecList result, branch[^1], n[0], branch
|
|
of nnkElse:
|
|
collectFieldsFromRecList result, branch[0], n[0], branch
|
|
else:
|
|
doAssert false
|
|
|
|
of nnkIdentDefs:
|
|
let fieldType = n[^2]
|
|
for i in 0 ..< n.len - 2:
|
|
var field: FieldDescription
|
|
field.name = n[i]
|
|
field.typ = fieldType
|
|
field.caseField = parentCaseField
|
|
field.caseBranch = parentCaseBranch
|
|
field.isDiscriminator = isDiscriminator
|
|
|
|
if field.name.kind == nnkPragmaExpr:
|
|
field.pragmas = field.name[1]
|
|
field.name = field.name[0]
|
|
|
|
if field.name.kind == nnkPostfix:
|
|
field.isPublic = true
|
|
field.name = field.name[1]
|
|
|
|
result.add field
|
|
|
|
of nnkSym:
|
|
result.add FieldDescription(
|
|
name: n,
|
|
typ: getType(n),
|
|
caseField: parentCaseField,
|
|
caseBranch: parentCaseBranch,
|
|
isDiscriminator: isDiscriminator)
|
|
|
|
of nnkNilLit, nnkDiscardStmt, nnkCommentStmt, nnkEmpty:
|
|
discard
|
|
|
|
else:
|
|
doAssert false, "Unexpected nodes in recordFields:\n" & n.treeRepr
|
|
|
|
proc collectFieldsInHierarchy(result: var seq[FieldDescription],
|
|
objectType: NimNode) =
|
|
var objectType = objectType
|
|
|
|
objectType.expectKind {nnkObjectTy, nnkRefTy}
|
|
|
|
if objectType.kind == nnkRefTy:
|
|
objectType = objectType[0]
|
|
|
|
objectType.expectKind nnkObjectTy
|
|
|
|
var baseType = objectType[1]
|
|
if baseType.kind != nnkEmpty:
|
|
baseType.expectKind nnkOfInherit
|
|
baseType = baseType[0]
|
|
baseType.expectKind nnkSym
|
|
baseType = getImpl(baseType)
|
|
baseType.expectKind nnkTypeDef
|
|
baseType = baseType[2]
|
|
baseType.expectKind {nnkObjectTy, nnkRefTy}
|
|
collectFieldsInHierarchy result, baseType
|
|
|
|
let recList = objectType[2]
|
|
collectFieldsFromRecList result, recList
|
|
|
|
proc recordFields*(typeImpl: NimNode): seq[FieldDescription] =
|
|
if typeImpl.isTuple:
|
|
for i in 1 ..< typeImpl.len:
|
|
result.add FieldDescription(typ: typeImpl[i], name: ident("Field" & $(i - 1)))
|
|
return
|
|
|
|
typeImpl.expectKind nnkTypeDef
|
|
collectFieldsInHierarchy(result, typeImpl[2])
|
|
|
|
macro field*(obj: typed, fieldName: static string): untyped =
|
|
newDotExpr(obj, ident fieldName)
|
|
|
|
proc skipPragma*(n: NimNode): NimNode =
|
|
if n.kind == nnkPragmaExpr: n[0]
|
|
else: n
|
|
|
|
proc getPragma(T: NimNode, lookedUpField: string, pragma: NimNode): NimNode =
|
|
let Tresolved = getType(T)[1]
|
|
if isTuple(Tresolved):
|
|
return nil
|
|
|
|
for f in recordFields(Tresolved.getImpl):
|
|
var fieldName = f.name
|
|
# TODO: Fix this in eqIdent
|
|
if fieldName.kind == nnkAccQuoted: fieldName = fieldName[0]
|
|
if eqIdent(fieldName, lookedUpField):
|
|
return f.pragmas.findPragma(pragma)
|
|
|
|
error "The type " & $Tresolved & " doesn't have a field named " & lookedUpField
|
|
|
|
macro getCustomPragmaFixed*(T: type, field: static string, pragma: typed{nkSym}): untyped =
|
|
result = nil
|
|
let p = getPragma(T, field, pragma)
|
|
|
|
if p != nil and p.len > 0:
|
|
if p.len == 2:
|
|
result = p[1]
|
|
else:
|
|
let def = p[0].getImpl[3]
|
|
result = newTree(nnkPar)
|
|
for i in 1 ..< def.len:
|
|
let key = def[i][0]
|
|
let val = p[i]
|
|
result.add newTree(nnkExprColonExpr, key, val)
|
|
|
|
macro hasCustomPragmaFixed*(T: type, field: static string, pragma: typed{nkSym}): untyped =
|
|
newLit(getPragma(T, field, pragma) != nil)
|
|
|
|
proc humaneTypeName*(typedescNode: NimNode): string =
|
|
var t = getType(typedescNode)[1]
|
|
if t.kind != nnkBracketExpr:
|
|
let tImpl = t.getImpl
|
|
if tImpl != nil and tImpl.kind notin {nnkEmpty, nnkNilLit}:
|
|
t = tImpl
|
|
|
|
repr(t)
|
|
|
|
macro inspectType*(T: typed): untyped =
|
|
echo "Inspect type: ", humaneTypeName(T)
|
|
|
|
# FIXED NewLit
|
|
|
|
proc newLitFixed*(c: char): NimNode {.compileTime.} =
|
|
## Produces a new character literal node.
|
|
result = newNimNode(nnkCharLit)
|
|
result.intVal = ord(c)
|
|
|
|
proc newLitFixed*(i: int): NimNode {.compileTime.} =
|
|
## Produces a new integer literal node.
|
|
result = newNimNode(nnkIntLit)
|
|
result.intVal = i
|
|
|
|
proc newLitFixed*(i: int8): NimNode {.compileTime.} =
|
|
## Produces a new integer literal node.
|
|
result = newNimNode(nnkInt8Lit)
|
|
result.intVal = i
|
|
|
|
proc newLitFixed*(i: int16): NimNode {.compileTime.} =
|
|
## Produces a new integer literal node.
|
|
result = newNimNode(nnkInt16Lit)
|
|
result.intVal = i
|
|
|
|
proc newLitFixed*(i: int32): NimNode {.compileTime.} =
|
|
## Produces a new integer literal node.
|
|
result = newNimNode(nnkInt32Lit)
|
|
result.intVal = i
|
|
|
|
proc newLitFixed*(i: int64): NimNode {.compileTime.} =
|
|
## Produces a new integer literal node.
|
|
result = newNimNode(nnkInt64Lit)
|
|
result.intVal = i
|
|
|
|
proc newLitFixed*(i: uint): NimNode {.compileTime.} =
|
|
## Produces a new unsigned integer literal node.
|
|
result = newNimNode(nnkUIntLit)
|
|
result.intVal = BiggestInt(i)
|
|
|
|
proc newLitFixed*(i: uint8): NimNode {.compileTime.} =
|
|
## Produces a new unsigned integer literal node.
|
|
result = newNimNode(nnkUInt8Lit)
|
|
result.intVal = BiggestInt(i)
|
|
|
|
proc newLitFixed*(i: uint16): NimNode {.compileTime.} =
|
|
## Produces a new unsigned integer literal node.
|
|
result = newNimNode(nnkUInt16Lit)
|
|
result.intVal = BiggestInt(i)
|
|
|
|
proc newLitFixed*(i: uint32): NimNode {.compileTime.} =
|
|
## Produces a new unsigned integer literal node.
|
|
result = newNimNode(nnkUInt32Lit)
|
|
result.intVal = BiggestInt(i)
|
|
|
|
proc newLitFixed*(i: uint64): NimNode {.compileTime.} =
|
|
## Produces a new unsigned integer literal node.
|
|
result = newNimNode(nnkUInt64Lit)
|
|
result.intVal = BiggestInt(i)
|
|
|
|
proc newLitFixed*(b: bool): NimNode {.compileTime.} =
|
|
## Produces a new boolean literal node.
|
|
result = if b: bindSym"true" else: bindSym"false"
|
|
|
|
proc newLitFixed*(s: string): NimNode {.compileTime.} =
|
|
## Produces a new string literal node.
|
|
result = newNimNode(nnkStrLit)
|
|
result.strVal = s
|
|
|
|
when false:
|
|
# the float type is not really a distinct type as described in https://github.com/nim-lang/Nim/issues/5875
|
|
proc newLitFixed*(f: float): NimNode {.compileTime.} =
|
|
## Produces a new float literal node.
|
|
result = newNimNode(nnkFloatLit)
|
|
result.floatVal = f
|
|
|
|
proc newLitFixed*(f: float32): NimNode {.compileTime.} =
|
|
## Produces a new float literal node.
|
|
result = newNimNode(nnkFloat32Lit)
|
|
result.floatVal = f
|
|
|
|
proc newLitFixed*(f: float64): NimNode {.compileTime.} =
|
|
## Produces a new float literal node.
|
|
result = newNimNode(nnkFloat64Lit)
|
|
result.floatVal = f
|
|
|
|
when declared(float128):
|
|
proc newLitFixed*(f: float128): NimNode {.compileTime.} =
|
|
## Produces a new float literal node.
|
|
result = newNimNode(nnkFloat128Lit)
|
|
result.floatVal = f
|
|
|
|
proc newLitFixed*(arg: enum): NimNode {.compileTime.} =
|
|
result = newCall(
|
|
arg.type.getTypeInst[1],
|
|
newLitFixed(int(arg))
|
|
)
|
|
|
|
proc newLitFixed*[N,T](arg: array[N,T]): NimNode {.compileTime.}
|
|
proc newLitFixed*[T](arg: seq[T]): NimNode {.compileTime.}
|
|
proc newLitFixed*[T](s: set[T]): NimNode {.compileTime.}
|
|
proc newLitFixed*(arg: tuple): NimNode {.compileTime.}
|
|
|
|
proc newLitFixed*(arg: object): NimNode {.compileTime.} =
|
|
result = nnkObjConstr.newTree(arg.type.getTypeInst[1])
|
|
for a, b in arg.fieldPairs:
|
|
result.add nnkExprColonExpr.newTree( newIdentNode(a), newLitFixed(b) )
|
|
|
|
proc newLitFixed*(arg: ref object): NimNode {.compileTime.} =
|
|
## produces a new ref type literal node.
|
|
result = nnkObjConstr.newTree(arg.type.getTypeInst[1])
|
|
for a, b in fieldPairs(arg[]):
|
|
result.add nnkExprColonExpr.newTree(newIdentNode(a), newLitFixed(b))
|
|
|
|
proc newLitFixed*[N,T](arg: array[N,T]): NimNode {.compileTime.} =
|
|
result = nnkBracket.newTree
|
|
for x in arg:
|
|
result.add newLitFixed(x)
|
|
|
|
proc newLitFixed*[T](arg: seq[T]): NimNode {.compileTime.} =
|
|
let bracket = nnkBracket.newTree
|
|
for x in arg:
|
|
bracket.add newLitFixed(x)
|
|
result = nnkPrefix.newTree(
|
|
bindSym"@",
|
|
bracket
|
|
)
|
|
if arg.len == 0:
|
|
# add type cast for empty seq
|
|
var typ = getTypeInst(typeof(arg))[1]
|
|
result = newCall(typ,result)
|
|
|
|
proc newLitFixed*[T](s: set[T]): NimNode {.compileTime.} =
|
|
result = nnkCurly.newTree
|
|
for x in s:
|
|
result.add newLitFixed(x)
|
|
|
|
proc newLitFixed*(arg: tuple): NimNode {.compileTime.} =
|
|
result = nnkPar.newTree
|
|
for a,b in arg.fieldPairs:
|
|
result.add nnkExprColonExpr.newTree(newIdentNode(a), newLitFixed(b))
|
|
|
|
iterator typedParams*(n: NimNode, skip = 0): (NimNode, NimNode) =
|
|
let params = n[3]
|
|
for i in (1 + skip) ..< params.len:
|
|
let paramNodes = params[i]
|
|
let paramType = paramNodes[^2]
|
|
|
|
for j in 0 ..< paramNodes.len - 2:
|
|
yield (skipPragma paramNodes[j], paramType)
|
|
|
|
iterator baseTypes*(exceptionType: NimNode): NimNode =
|
|
var typ = exceptionType
|
|
while typ != nil:
|
|
let impl = getImpl(typ)
|
|
if impl.len != 3 or impl[2].kind != nnkObjectTy:
|
|
break
|
|
|
|
let objType = impl[2]
|
|
if objType[1].kind != nnkOfInherit:
|
|
break
|
|
|
|
typ = objType[1][0]
|
|
yield typ
|
|
|
|
macro unpackArgs*(callee: untyped, args: untyped): untyped =
|
|
result = newCall(callee)
|
|
for arg in args:
|
|
let arg = if arg.kind == nnkHiddenStdConv: arg[1]
|
|
else: arg
|
|
if arg.kind == nnkArgList:
|
|
for subarg in arg:
|
|
result.add subarg
|
|
else:
|
|
result.add arg
|
|
|
|
template genExpr*(treeType: NimNodeKind, body: untyped): untyped =
|
|
iterator generator: NimNode = body
|
|
|
|
macro payload: untyped =
|
|
result = newTree(treeType)
|
|
for node in generator():
|
|
result.add node
|
|
|
|
payload()
|
|
|
|
template genStmtList*(body: untyped) =
|
|
iterator generator: NimNode = body
|
|
|
|
macro payload: untyped =
|
|
result = newStmtList()
|
|
for node in generator():
|
|
result.add node
|
|
|
|
payload()
|
|
|
|
template genSimpleExpr*(body: untyped): untyped =
|
|
macro payload: untyped = body
|
|
payload()
|