Search code examples
macrosmetaprogrammingnim-lang

How to evaluate an expression in nim?


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!


Solution

  • 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.