如何评估 nim 中的表达式?

How to evaluate an expression in nim?

我正在尝试在 nim 中执行与 Python 中的 eval 方法等效的方法。 我的印象是 macros 包中的 parseStmt 应该可以帮助我解决这个问题,但我遇到了一个我不明白的编译问题。

import macros

echo parseStmt("1 + 2")

我原以为它会在执行时打印 3,但编译却抱怨说

Error: request to generate code for .compileTime proc: $

我发现 this thread,并且那里的示例有效,并且在此之后,我能够使以下程序按我预期的方式工作:

import macros
import strformat

macro eval(value: string): untyped =
  result = parseStmt fmt"{value}"

echo eval("1+2")

但我完全不明白为什么要这样写。如果我内联语句 let value = "1 + 2"; echo parseStmt fmt"{value}",我会得到与上面相同的编译错误。

另外,在上面的 eval 宏的上下文中,为什么 parseStmt valueparseStmt fmt"{value}" 不同?

我在这里错过了什么?

提前感谢您的澄清!

注意:虽然此答案概述了为什么会发生这种情况,但请记住,您尝试做的事情可能不会产生您想要的结果(我假设是表达式的运行时评估)。

您正在尝试将 NimNode 传递给需要 stringparseStmtfmt 宏自动将 {} 中的任何内容字符串化,您可以通过执行 $value 将节点转换为字符串来省略 fmt

正如我已经指出的,这个 将不会 像在 Python 中那样工作:Nim 没有运行时评估。字符串中的表达式将在编译时进行计算,因此像这样的简单示例将不会做你想做的事:

import std/rdstdin
let x = readLineFromStdin(">")
echo eval(x)

为什么?

首先,因为您正在对传递给 eval 的 AST 进行字符串化,所以要传递给宏的不是 x 变量后面的 string - 它将成为表示 x 变量的符号。如果您将一个符号字符串化,您将获得底层标识符,这意味着 parseStmt 将接收 "x" 作为其参数。这将影响存储在 x 中的字符串被打印出来,这是错误的。

您想要的是以下内容:

import std/rdstdin
import std/macros

macro eval(value: static string): untyped =
  result = parseStmt(value)

echo eval("1 + 2")

这可以防止将运行时已知的值传递给宏。您现在只能将 const 和文字传递给它,这是正确的行为。

不像 Python 是一种解释型语言,Nim 是编译型的。这意味着所有代码都在编译时被解析并转换为机器码,而你最终得到的程序对 Nim 根本一无所知(至少只要你不将 Nim 编译器导入为模块,这是可能的)。所以 Nim 中的 parseStmt 和所有 macro/template 扩展内容都是在编译期间完成的。该错误虽然可能有点难以阅读,但它试图告诉您传递给 $ 的内容(这是 Nim 中的转换为字符串的运算符,并由 echo 在其所有arguments) 是编译时的东西,不能在 运行time 上使用。在这种情况下,这是因为 parseStmt 不是 return "3",它 return 类似于 NimNode(kind: nnkIntLit, intVal: 3),并且 NimNode 类型仅可用在编译期间。然而,Nim 允许您在编译时将 运行 代码转换为 return 其他代码,这就是宏的作用。您在那里编写的 eval 宏采用 value,这是在 运行 时间内解析为字符串的任何语句,作为 NimNode 传递。这也是 result = parseStmt value 在您的情况下不起作用的原因,因为 value 还不是字符串,但可能类似于从标准输入读取字符串,这会在 [=43 期间产生字符串=]时间。然而,这里使用 strformat 有点混乱和矫枉过正。如果您将代码更改为:

import macros

macro eval(value: static[string]): untyped =
  result = parseStmt value

echo eval("1+2")

它将正常工作。这是因为我们现在已经告诉 Nim value 需要是 static 即在编译时已知。在这种情况下,字符串文字 "1+2" 显然在编译时已知,但这也可能是对编译时过程的调用,甚至是 staticRead 的输出,它在编译期间读取文件。

如您所见,Nim 非常强大,但编译时和 运行-时之间的障碍有时会让人有些困惑。另请注意,您的 eval 过程实际上并没有在编译时评估任何东西,它只是 return 的 Nim 代码 1 + 2 所以您的代码最终是 echo 1 + 2。如果您想在编译时实际 运行 代码,您可能需要查看编译时过程。

希望这有助于阐明您的问题。