Hi!
If I have for example the code like this:
import typeinfo
proc testProc(s: string) =
echo "do something with ", s
var a = testProc
var anyProc = toAny(a)
echo(kind(anyProc))
???
What should I use in place of ??? to get the parameters list of the procedure?
Is there some reflection API in nim to call the testProc via anyProc variable?
try this:
import macros
... # your code here
macro test(n: typed): untyped =
let x = getImpl(n.symbol)
echo x.treeRepr
... # do whatever you like with the x
test(testProc)
macro is better than typeinfo
Good, thank you! I'll try macros.
But is it possible (in principle) to call the testProc using its name and "parameter description"?
I want to implement a procedure callProc looking like:
callProc("testProc", encodedParams)
Yes, you can do this. You need to store proc address somewhere and it's args in simple case.
Essentially, you need to wrap this code into nice macro:
proc testProc(abc: int, bca: int) {.cdecl.} =
echo "testProc: ", abc, ", ", bca
var testProcAddr = cast[pointer](testProc)
var testProcCall = cast[proc(a: int, b: int) {.cdecl.}](testProcAddr)
testProcCall(6, 7)
I created this sample macro for you. This isn't ideal, actually this is pretty crappy but you get an idea how it works. Code doesn't support any argument types other than int and requires cdecl calling convention. callProc proc is actually should be generated by macro.
import macros, strutils
type
ProcInfo = ref object
name: string
procAddr: pointer
args: seq[string]
# callConv: int
var procInfos: seq[ProcInfo] = @[]
proc anotherTestProc(abc: int) {.cdecl.} =
echo "anotherTestProc: ", abc
proc testProc(abc: int, bca: int) {.cdecl.} =
echo "testProc: ", abc, ", ", bca
macro registerProc(p: typed): typed =
let impl = getImpl(p.symbol)
#echo impl.treeRepr
var xprocName = impl[0].symbol.`$`
var xprocArgs: seq[string] = @[]
for formalParam in impl.findChild(it.kind == nnkFormalParams):
if formalParam.kind != nnkIdentDefs:
continue
var argSym = formalParam[1]
xprocArgs.add(argSym.symbol.`$`)
result = parseStmt("""
block:
var prcInfo = new(ProcInfo)
prcInfo.name = "$1"
prcInfo.args = @$2
prcInfo.procAddr = cast[pointer]($3)
procInfos.add(prcInfo)
""" % [xprocName, xProcArgs.repr, xprocName])
#echo result.treeRepr
proc findProcInfo(procName: string, argCount: int): ProcInfo =
for procInfo in procInfos:
if procInfo.name == procName and
procInfo.args.len == argCount:
return procInfo
return nil
proc callProc(procName: string, args: varargs[int]) =
var p = findProcInfo(procName, args.len)
assert(p != nil)
case args.len:
of 0:
var procToCall = cast[proc() {.cdecl.}](p.procAddr)
procToCall()
of 1:
var procToCall = cast[proc(a: int) {.cdecl.}](p.procAddr)
procToCall(args[0])
of 2:
var procToCall = cast[proc(a: int, b: int) {.cdecl.}](p.procAddr)
procToCall(args[0], args[1])
else: discard
registerProc(testProc)
registerProc(anotherTestProc)
callProc("testProc", 123, 321)
callProc("anotherTestProc", 666)
Yes, you can do this. You need to store proc address somewhere and it's args in simple case. > Essentially, you need to wrap this code into nice macro: > ...
Thank you for the answer. Yes, it's possible to call functions with predefined argument types/count. My question is more about the logic looking like this (in C++):
template<class F, class... Args>
void saveForExecute(F f, Args... args) {
auto pHolder = getSuitableHolder();
pHolder->addTask(std::bind(f, args...));
}
saveForExecute(&test1, 10, 20);
CTest *pTest = new CTest();
saveForExecute(&CTest::test2, pTest, "Test2");
And for example with reflection API that would be possible. As I understand in nim with macros it's possible to get all parameters and its types from AST. But I'm afraid this solution isn't stable enough: what if AST internal logic is slightly changed in nim 1.0 or even in 2.5?