I'm trying to perform the equivalent of the eval
method from Python in nim.
I was under the impression that parseStmt
from the macros
package should help me with this, but I'm facing a compilation issue that I don't understand.
import macros
echo parseStmt("1 + 2")
I would have expected this to print 3
when executed, but instead the compilation complains that
Error: request to generate code for .compileTime proc: $
I found this thread, and the examples there work, and following this, I was able to make the following program that works as I would expect:
import macros
import strformat
macro eval(value: string): untyped =
result = parseStmt fmt"{value}"
echo eval("1+2")
But I don't undertand why it needs to be written in this way at all. If I inline the statement, let value = "1 + 2"; echo parseStmt fmt"{value}"
, I get the same compile error as above.
Also, why is parseStmt value
different from parseStmt fmt"{value}"
, in the context of the eval
macro above?
What am I missing here?
Thank you in advance for any clarifications!
Unlike Python which is an interpreted language, Nim is compiled. This means that all code is parsed and turned into machine code on compile-time and the program that you end up with doesn't really know anything about Nim at all (at least as long as you don't import the Nim compiler as a module, which is possible). So parseStmt
and all macro/template expansion stuff in Nim is done completely during compilation. The error, although maybe a bit hard to read, is trying to tell you that what was passed to $
(which is the convert-to-string operator in Nim, and called by echo
on all its arguments) is a compile-time thing that can't be used on runtime. In this case it's because parseStmt
doesn't return "3"
, it returns something like NimNode(kind: nnkIntLit, intVal: 3)
, and the NimNode
type is only available during compile-time. Nim however allows you to run code on compile-time to return other code, this is what a macro does. The eval
macro you wrote there takes value
which is any statement that resolves to a string during runtime, passed as a NimNode
. This is also why result = parseStmt value
won't work in your case, because value
is not yet a string, but could be something like reading a string from standard input which would result in a string during runtime. However the use of strformat
here is a bit confusing and overkill. If you change your code to:
import macros
macro eval(value: static[string]): untyped =
result = parseStmt value
echo eval("1+2")
It will work just fine. This is because we have now told Nim that value
needs to be a static
i.e. known during compile-time. In this case the string literal "1+2"
is obviously known at compile-time, but this could also be a call to a compile-time procedure, or even the output of staticRead
which reads a file during compilation.
As you can see Nim is very powerful, but the barrier between compile-time and run-time can sometimes be a bit confusing. Note also that your eval
procedure doesn't actually evaluate anything at compile-time, it simply returns the Nim code 1 + 2
so your code ends up being echo 1 + 2
. If you want to actually run the code at compile-time you might want to look into compile-time procedures.
Hope this helps shed some light on your issue.
Note: while this answer outlines why this happens, keep in mind that what you're trying to do probably won't result in what you want (which I assumed to be runtime evaluation of expressions).
You're trying to pass a NimNode
to parseStmt
which expects a string
. The fmt
macro automatically stringifies anything in the {}
, you can omit the fmt
by doing $value
to turn the node into a string.
As I already noted, this will not work as it does in Python: Nim does not have runtime evaluation. The expression in the string is going to be evaluated at compile time, so a simple example like this will not do what you want:
import std/rdstdin
let x = readLineFromStdin(">")
echo eval(x)
First of all, because you're stringifying the AST you pass to the eval
, it's not the string
behind the x
variable that's going to get passed to the macro - it's going to be the symbol that denotes the x
variable. If you stringify a symbol, you get the underlying identifier, which means that parseStmt
will receive "x"
as its parameter. This will effect in the string stored in x
being printed out, which is wrong.
What you want instead is the following:
import std/rdstdin
import std/macros
macro eval(value: static string): untyped =
result = parseStmt(value)
echo eval("1 + 2")
This prevents runtime-known values from being passed to the macro. You can only pass const
s and literals to it now, which is the correct behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With