-
-
Save demotomohiro/8c7c11516df46f009339704bfb713cf6 to your computer and use it in GitHub Desktop.
| import macros, strutils | |
| # Some string that no one use for variable name. | |
| # It was generated with following command: | |
| # openssl rand -base64 16 | |
| const fieldMarker = "Ew55GZ4wPFop9pbAYp9RQ" | |
| macro fields*(stmts: untyped): untyped = | |
| stmts.expectKind nnkStmtList | |
| result = newTree( | |
| nnkVarSection, | |
| newIdentDefs(ident(fieldMarker), ident"int")) | |
| for i in stmts: | |
| if i.kind == nnkAsgn: | |
| result.add newIdentDefs(i[0], newEmptyNode(), i[1]) | |
| elif i.kind == nnkVarSection: | |
| i.copyChildrenTo result | |
| elif i.kind == nnkCommentStmt: | |
| continue | |
| else: | |
| error "You can use only assignment and var section to `field` macro.", stmts | |
| #Add used pragma to all ident. | |
| for i, son in result.pairs: | |
| son.expectKind nnkIdentDefs | |
| for j in 0..(son.len - 3): | |
| let identNode = newTree( | |
| nnkPragmaExpr, | |
| son[j], | |
| newTree(nnkPragma, ident"used")) | |
| result[i][j] = identNode | |
| macro objectDef*(procDef: typed): untyped = | |
| proc isFieldSect(n: NimNode): bool = | |
| n.kind == nnkVarSection and | |
| n.len != 0 and | |
| n[0].kind == nnkIdentDefs and | |
| n[0][0].strVal == fieldMarker | |
| proc readField(recList, src: NimNode) = | |
| if isFieldSect(src): | |
| for i in src: | |
| i.expectKind nnkIdentDefs | |
| if i[0].strVal == fieldMarker: | |
| continue | |
| if i[^1].kind == nnkEmpty: | |
| recList.add i.copy | |
| else: | |
| let identDefs = i.copy | |
| identDefs[^2] = i[^1].getTypeInst | |
| identDefs[^1] = newEmptyNode() | |
| recList.add identDefs | |
| proc ToAsgnResult(field: NimNode): NimNode = | |
| proc toIdentsRecur(src: NimNode) = | |
| for i, n in src: | |
| if n.kind == nnkSym and n.getImpl.kind == nnkNilLit: | |
| src[i] = ident(n.strVal) | |
| else: | |
| toIdentsRecur(n) | |
| var newBlock = newStmtList() | |
| for j in field: | |
| j.expectKind nnkIdentDefs | |
| if j[^1].kind == nnkEmpty: | |
| continue | |
| else: | |
| let left = newDotExpr( | |
| ident"result", | |
| ident(j[0].strVal)) | |
| #Convert symbols that refer to local variable or argument to ident. | |
| toIdentsRecur(j[2]) | |
| newBlock.add newAssignment(left, j[2]) | |
| newBlockStmt(newBlock) | |
| procDef.expectKind nnkProcDef | |
| let name = procDef.name.strVal | |
| assert name.startsWith("the") | |
| var objName = name | |
| objName.removePrefix("the") | |
| let objNameIdent = ident(objName) | |
| var recList = newNimNode(nnkRecList, procDef) | |
| if procDef.body.kind == nnkVarSection: | |
| recList.readField(procDef.body) | |
| else: | |
| for i in procDef.body: | |
| recList.readField(i) | |
| if recList.len == 0: | |
| warning "There is no field", procDef | |
| for i in recList: | |
| i[0] = ident(i[0].strVal) | |
| #echo "recList.repr:" | |
| #echo recList.treeRepr | |
| let typDef = quote do: | |
| type `objNameIdent` = object | |
| typDef[0][2][2] = recList | |
| #echo "typDef:" | |
| #echo typDef.treeRepr | |
| let newProcDef = procDef.copy | |
| newProcDef.name = ident("init" & objName) | |
| newProcDef.params[0] = objNameIdent | |
| if isFieldSect(newProcDef.body): | |
| newProcDef.body = newProcDef.body.ToAsgnResult() | |
| else: | |
| for i, n in newProcDef.body.pairs: | |
| if isFieldSect(n): | |
| newProcDef.body[i] = n.ToAsgnResult() | |
| #echo "newProcDef.treeRepr" | |
| #echo newProcDef.treeRepr | |
| newStmtList(typDef, newProcDef) | |
| when isMainModule: | |
| import sets | |
| when false: | |
| expandMacros: | |
| fields: | |
| x = 1 + 1 | |
| var | |
| a, b, c: int | |
| s, t = "aa" | |
| expandMacros: | |
| proc theFoo() {.objectDef, used.} = | |
| fields: | |
| ## field | |
| x = 1 + 2 | |
| y = "foo " & "bar" | |
| # const n = 1.1 | |
| # result.z = test(n) | |
| expandMacros: | |
| proc theBar(arg: int) {.objectDef, used.} = | |
| var a = 1 | |
| let ary = ['a', 'b', 'c'] | |
| fields: | |
| b = a + 2 | |
| c = initHashSet[int](arg) | |
| d = toHashSet(ary) | |
| var x = 1.0 | |
| var | |
| i,j,k: array[3, uint] | |
| y: int | |
| let a = initFoo() | |
| echo a.repr | |
| let b = initBar(4) | |
| doAssert b.b == 3 | |
| doAssert b.c is HashSet[int] | |
| doAssert b.c.len == 0 | |
| doAssert b.d == toHashSet(['a', 'b', 'c']) | |
| doAssert b.x == 1.0 | |
| doAssert b.i is array[3, uint] | |
| echo b.repr |
いいえ!
\tiny hope this is correct
if I can use getTypeInst from untyped parameters
This works, but only under specific circumstances.
- determining the type of an arbitrary untyped expression is not possible from an untyped marco, as far as I can tell
- for procs, global vars etc. we can use
bindSymand applygetType*on that symbol to access the type information that way, e.g.
import macros
proc testA(x: int): float =
discard
macro stuff(callStuff: untyped): untyped =
let procSym = bindSym("testA")
echo procSym.getTypeInst.treeRepr
stuff(5)which prints
ProcTy
FormalParams
Sym "float"
IdentDefs
Sym "x"
Sym "int"
Empty
Empty
But yes, in your example the problem as far as I see it would be to deduce the types of expressions like x = 1 + 1, which cannot be done that way unfortunately (maybe there's some hacky workaround, not sure).
or call a macro with typed parameters from macro with untyped parameters.
This can be done in a way too, but only by generating the call to the typed expression from the untyped macro, like:
import macros
macro typedStuff(exp: typed): untyped =
let impl = exp.getTypeInst
echo impl.treeRepr
# call typed from untyped
macro moreStuff(cmds: untyped): untyped =
result = newStmtList()
let ts = ident"typedStuff"
for cmd in cmds:
let id = cmd[0]
let arg = cmd[1]
result.add quote do:
let `id` = `arg`
`ts` `id`
echo result.treeRepr
moreStuff:
x = 1 + 1
y = "foo" & "bar"Sorry, for the really bad example. Coming up with macro examples without a use case in mind is hard for me, haha. And I can't wrap my head around whether this can be applied to your macro in some way to make it easier.
Thank you!
Your example code is nice!
I didn't know we can use bindSym and apply getType* on that symbol.
I thought this code could be simpler if I can use getTypeInst from untyped parameters or call a macro with typed parameters from macro with untyped parameters.
But it seems these are impossible.
nim-lang/Nim#7739
nim-lang/RFCs#44