268 lines
7.3 KiB
Nim
268 lines
7.3 KiB
Nim
|
import macros
|
||
|
import strformat
|
||
|
import strutils
|
||
|
import sequtils; export sequtils
|
||
|
|
||
|
type
|
||
|
UnfinishedObjectTypeDef* = object
|
||
|
root*: NimNode
|
||
|
insertion*: NimNode
|
||
|
|
||
|
UnfinishedCase* = object
|
||
|
root*: NimNode
|
||
|
cases*: seq[NimNode]
|
||
|
elsebody*: NimNode
|
||
|
|
||
|
UnfinishedIf = object
|
||
|
root*: NimNode
|
||
|
elsebody*: NimNode
|
||
|
|
||
|
InsertionPoint = object
|
||
|
parent*: NimNode
|
||
|
child*: NimNode
|
||
|
|
||
|
const ident* = (proc(s: string): NimNode)(ident)
|
||
|
const newIdentNode* = (proc(s: string): NimNode)(newIdentNode)
|
||
|
|
||
|
proc replaceNodes*(ast: NimNode): NimNode =
|
||
|
## Replace NimIdent and NimSym by a fresh ident node
|
||
|
##
|
||
|
## Use with the results of ``quote do: ...`` to get
|
||
|
## ASTs without symbol resolution having been done already.
|
||
|
proc inspect(node: NimNode): NimNode =
|
||
|
case node.kind:
|
||
|
of nnkIdent:
|
||
|
if "`gensym" in node.strVal:
|
||
|
return ident(node.strVal.split("`")[0])
|
||
|
else:
|
||
|
return ident(node.strVal)
|
||
|
of nnkSym:
|
||
|
return ident(node.strVal)
|
||
|
of nnkEmpty:
|
||
|
return node
|
||
|
of nnkLiterals:
|
||
|
return node
|
||
|
of nnkOpenSymChoice:
|
||
|
return inspect(node[0])
|
||
|
else:
|
||
|
var rTree = node.kind.newTree()
|
||
|
for child in node:
|
||
|
rTree.add inspect(child)
|
||
|
return rTree
|
||
|
result = inspect(ast)
|
||
|
|
||
|
proc parentOf*(node: NimNode, name:string): InsertionPoint =
|
||
|
## Recursively search for an ident node of the given name and return
|
||
|
## the parent of that node.
|
||
|
var stack:seq[NimNode] = @[node]
|
||
|
while stack.len > 0:
|
||
|
var n = stack.pop()
|
||
|
for child in n.children:
|
||
|
if child.kind == nnkIdent and child.strVal == name:
|
||
|
return InsertionPoint(parent:n, child:child)
|
||
|
else:
|
||
|
stack.add(child)
|
||
|
error("node not found: " & name)
|
||
|
|
||
|
proc parentOf*(node: NimNode, child:NimNode): InsertionPoint =
|
||
|
## Recursively search for an ident node of the given name and return
|
||
|
## the parent of that node.
|
||
|
var stack:seq[NimNode] = @[node]
|
||
|
while stack.len > 0:
|
||
|
var n = stack.pop()
|
||
|
for c in n.children:
|
||
|
if c == child:
|
||
|
return InsertionPoint(parent:n, child:c)
|
||
|
else:
|
||
|
stack.add(c)
|
||
|
error("node not found: " & child.repr)
|
||
|
|
||
|
proc getInsertionPoint*(node: var NimNode, name:string): InsertionPoint =
|
||
|
## Return a node pair that you can replace with something else
|
||
|
return node.parentOf(name)
|
||
|
|
||
|
proc clear*(point: InsertionPoint):int =
|
||
|
var i = 0
|
||
|
for child in point.parent.children:
|
||
|
if child == point.child:
|
||
|
break
|
||
|
inc(i)
|
||
|
point.parent.del(i, 1)
|
||
|
result = i
|
||
|
|
||
|
proc replace*(point: InsertionPoint, newnode: NimNode) =
|
||
|
## Replace the child
|
||
|
let i = point.clear()
|
||
|
point.parent.insert(i, newnode)
|
||
|
|
||
|
proc newObjectTypeDef*(name: string, isref:bool = false): UnfinishedObjectTypeDef {.compileTime.} =
|
||
|
## Creates:
|
||
|
## root ->
|
||
|
## type
|
||
|
## {name} = object
|
||
|
## insertion -> ...
|
||
|
##
|
||
|
var insertion = newNimNode(nnkRecList)
|
||
|
var objectty = nnkObjectTy.newTree(
|
||
|
newEmptyNode(),
|
||
|
newEmptyNode(),
|
||
|
insertion,
|
||
|
)
|
||
|
if isref:
|
||
|
objectty = nnkRefTy.newTree(objectty)
|
||
|
var root = newNimNode(nnkTypeSection).add(
|
||
|
newNimNode(nnkTypeDef).add(
|
||
|
ident(name),
|
||
|
newEmptyNode(),
|
||
|
objectty
|
||
|
)
|
||
|
)
|
||
|
result = UnfinishedObjectTypeDef(root: root, insertion: insertion)
|
||
|
|
||
|
proc addObjectField*(objtypedef: UnfinishedObjectTypeDef, name: string, kind: NimNode) {.compileTime.} =
|
||
|
## Adds a field to an object definition created by newObjectTypeDef
|
||
|
objtypedef.insertion.add(newIdentDefs(
|
||
|
newNimNode(nnkPostfix).add(
|
||
|
ident("*"),
|
||
|
ident(name),
|
||
|
),
|
||
|
kind,
|
||
|
newEmptyNode(),
|
||
|
))
|
||
|
|
||
|
proc addObjectField*(objtypedef: UnfinishedObjectTypeDef, name: string, kind: string, isref: bool = false) {.compileTime.} =
|
||
|
## Adds a field to an object definition created by newObjectTypeDef
|
||
|
if isref:
|
||
|
addObjectField(objtypedef, name, nnkRefTy.newTree(ident(kind)))
|
||
|
else:
|
||
|
addObjectField(objtypedef, name, ident(kind))
|
||
|
|
||
|
#--------------------------------------------------------------
|
||
|
# case statements
|
||
|
#--------------------------------------------------------------
|
||
|
proc newCaseStatement*(key: NimNode): ref UnfinishedCase =
|
||
|
## Create a new, unfinished case statement. Call `finalize` to finish it.
|
||
|
##
|
||
|
## case(`key`)
|
||
|
new(result)
|
||
|
result.root = nnkCaseStmt.newTree(key)
|
||
|
|
||
|
proc newCaseStatement*(key: string): ref UnfinishedCase =
|
||
|
return newCaseStatement(ident(key))
|
||
|
|
||
|
proc add*(n: ref UnfinishedCase, opt: seq[NimNode], body: NimNode) =
|
||
|
## Adds a branch to an UnfinishedCase
|
||
|
##
|
||
|
## Usage:
|
||
|
## var c = newCaseStatement("foo")
|
||
|
## c.add(@[newLit("apple"), newLit("banana")], quote do:
|
||
|
## echo "apple or banana"
|
||
|
## )
|
||
|
var branch = nnkOfBranch.newTree()
|
||
|
for node in opt:
|
||
|
branch.add(node)
|
||
|
branch.add(body)
|
||
|
n.cases.add(branch)
|
||
|
|
||
|
proc add*(n: ref UnfinishedCase, opt: NimNode, body: NimNode) =
|
||
|
## Adds a branch to an UnfinishedCase
|
||
|
n.add(@[opt], body)
|
||
|
|
||
|
proc add*(n: ref UnfinishedCase, opt:string, body: NimNode) =
|
||
|
## Adds a branch to an UnfinishedCase
|
||
|
##
|
||
|
## c.add("foo", quote do:
|
||
|
## echo "value was foo"
|
||
|
## )
|
||
|
n.add(@[newStrLitNode(opt)], body)
|
||
|
|
||
|
proc add*(n: ref UnfinishedCase, opts: seq[string], body: NimNode) =
|
||
|
## Adds a branch to an UnfinishedCase
|
||
|
##
|
||
|
## c.add(@["foo", "foo-also"], quote do:
|
||
|
## echo "value was foo"
|
||
|
## )
|
||
|
n.add(opts.mapIt(newStrLitNode(it)), body)
|
||
|
|
||
|
proc add*(n: ref UnfinishedCase, opt:int, body: NimNode) =
|
||
|
## Adds an integer branch to an UnfinishedCase
|
||
|
add(n, @[newLit(opt)], body)
|
||
|
|
||
|
proc hasElse*(n: ref UnfinishedCase): bool =
|
||
|
not n.elsebody.isNil
|
||
|
|
||
|
proc addElse*(n: ref UnfinishedCase, body: NimNode) =
|
||
|
## Add an else: to an UnfinishedCase
|
||
|
n.elsebody = body
|
||
|
|
||
|
proc isValid*(n: ref UnfinishedCase): bool =
|
||
|
return n.cases.len > 0 or n.elsebody != nil
|
||
|
|
||
|
proc finalize*(n: ref UnfinishedCase): NimNode =
|
||
|
if n.cases.len > 0:
|
||
|
for branch in n.cases:
|
||
|
n.root.add(branch)
|
||
|
if n.elsebody != nil:
|
||
|
n.root.add(nnkElse.newTree(n.elsebody))
|
||
|
result = n.root
|
||
|
else:
|
||
|
result = n.elsebody
|
||
|
|
||
|
#--------------------------------------------------------------
|
||
|
# if statements
|
||
|
#--------------------------------------------------------------
|
||
|
|
||
|
proc newIfStatement*(): ref UnfinishedIf =
|
||
|
## Create an unfinished if statement.
|
||
|
new(result)
|
||
|
result.root = nnkIfStmt.newTree()
|
||
|
|
||
|
proc add*(n: ref UnfinishedIf, cond: NimNode, body: NimNode) =
|
||
|
## Add a branch to an if statement
|
||
|
##
|
||
|
## var f = newIfStatement()
|
||
|
## f.add()
|
||
|
add(n.root, nnkElifBranch.newTree(
|
||
|
cond,
|
||
|
body,
|
||
|
))
|
||
|
|
||
|
proc addElse*(n: ref UnfinishedIf, body: NimNode) =
|
||
|
## Add an else: to an UnfinishedIf
|
||
|
n.elsebody = body
|
||
|
|
||
|
proc isValid*(n: ref UnfinishedIf): bool =
|
||
|
return n.root.len > 0 or n.elsebody != nil
|
||
|
|
||
|
proc finalize*(n: ref UnfinishedIf): NimNode =
|
||
|
## Finish an If statement
|
||
|
result = n.root
|
||
|
if n.root.len == 0:
|
||
|
# This "if" is only an "else"
|
||
|
result = n.elsebody
|
||
|
elif n.elsebody != nil:
|
||
|
result.add(nnkElse.newTree(n.elsebody))
|
||
|
|
||
|
proc nimRepr*(n:NimNode): string =
|
||
|
case n.kind
|
||
|
of nnkStmtList:
|
||
|
var lines:seq[string]
|
||
|
for child in n:
|
||
|
lines.add(child.nimRepr)
|
||
|
result = lines.join("\L")
|
||
|
of nnkCommand:
|
||
|
let name = n[0].nimRepr
|
||
|
var args:seq[string]
|
||
|
for i, child in n:
|
||
|
if i == 0:
|
||
|
continue
|
||
|
args.add(child.nimRepr)
|
||
|
echo n.lispRepr
|
||
|
let arglist = args.join(", ")
|
||
|
result = &"{name}({arglist})"
|
||
|
of nnkIdent:
|
||
|
result = n.strVal
|
||
|
of nnkStrLit:
|
||
|
result = "[" & n.strVal & "]"
|
||
|
else:
|
||
|
result = &"<unknown {n.kind} {n.lispRepr}>"
|