Я пытаюсь выполнить эквивалент метода eval из Python в nim.
У меня сложилось впечатление, что parseStmt из пакета macros должен помочь мне в этом, но я столкнулся с проблемой компиляции, которую не понимаю.
import macros
echo parseStmt("1 + 2")
Я ожидал, что это напечатает 3 при выполнении, но вместо этого компиляция жалуется, что
Error: request to generate code for .compileTime proc: $
Я нашел эту тему, и примеры там работают, и следуя этому, я смог сделать следующую программу, которая работает так, как я ожидал:
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}", я получу ту же ошибку компиляции, что и выше.
Кроме того, почему parseStmt value отличается от parseStmt fmt"{value}" в контексте макроса eval выше?
Что мне здесь не хватает?
Заранее благодарен за любые разъяснения!





Примечание. Хотя в этом ответе объясняется, почему это происходит, имейте в виду, что то, что вы пытаетесь сделать, вероятно, не приведет к тому, что вы хотите (что я предположил, что это оценка выражений во время выполнения).
Вы пытаетесь передать NimNode в parseStmt, который ожидает string. Макрос fmt автоматически преобразует в строку что-либо в {}, вы можете опустить fmt, выполнив $value, чтобы превратить узел в строку.
Как я уже заметил, это не будет работать так, как в Python: в Nim нет оценки во время выполнения. Выражение в строке будет оцениваться во время компиляции, поэтому такой простой пример не будет делать то, что вы хотите:
import std/rdstdin
let x = readLineFromStdin(">")
echo eval(x)
Прежде всего, поскольку вы преобразуете AST, который вы передаете в eval, макросу передается не string за переменной x — это будет символ, обозначающий переменную x. Если вы преобразуете символ в строку, вы получите базовый идентификатор, а это означает, что parseStmt получит "x" в качестве своего параметра. Это приведет к тому, что строка, хранящаяся в x, будет распечатана, что неверно.
Вместо этого вы хотите следующее:
import std/rdstdin
import std/macros
macro eval(value: static string): untyped =
result = parseStmt(value)
echo eval("1 + 2")
Это предотвращает передачу в макрос значений, известных во время выполнения. Теперь вы можете передавать ему только consts и литералы, что является правильным поведением.
В отличие от Python, который является интерпретируемым языком, Nim компилируется. Это означает, что весь код анализируется и превращается в машинный код во время компиляции, а программа, которая у вас получается, вообще ничего не знает о Nim (по крайней мере, до тех пор, пока вы не импортируете компилятор Nim как модуль, что возможно). Так что parseStmt и все расширение макроса/шаблона в Nim делается полностью во время компиляции. Ошибка, хотя, возможно, немного трудно читаемая, пытается сказать вам, что то, что было передано в $ (который является оператором преобразования в строку в Nim и вызывается echo для всех его аргументов), является вещью времени компиляции. которые нельзя использовать во время выполнения. В данном случае это потому, что parseStmt не возвращает "3", он возвращает что-то вроде NimNode(kind: nnkIntLit, intVal: 3), а тип NimNode доступен только во время компиляции. Однако Nim позволяет вам запускать код во время компиляции, чтобы вернуть другой код, это то, что делает макрос. Макрос eval, который вы там написали, принимает value, то есть любой оператор, который разрешается в строку во время выполнения, передаваемый как NimNode. Вот почему result = parseStmt value не будет работать в вашем случае, потому что value еще не является строкой, но может быть чем-то вроде чтения строки из стандартного ввода, что приведет к строке во время выполнения. Однако использование strformat здесь немного сбивает с толку и излишне. Если вы измените свой код на:
import macros
macro eval(value: static[string]): untyped =
result = parseStmt value
echo eval("1+2")
Это будет работать просто отлично. Это потому, что мы сказали Ниму, что value должен быть static, то есть известным во время компиляции. В этом случае строковый литерал "1+2", очевидно, известен во время компиляции, но это также может быть вызов процедуры времени компиляции или даже вывод staticRead, который считывает файл во время компиляции.
Как видите, Nim очень мощен, но барьер между временем компиляции и временем выполнения иногда может немного сбивать с толку. Также обратите внимание, что ваша процедура eval на самом деле ничего не оценивает во время компиляции, она просто возвращает код Nim 1 + 2, поэтому ваш код в конечном итоге будет echo 1 + 2. Если вы хотите фактически запустить код во время компиляции, вы можете изучить процедуры времени компиляции.
Надеюсь, это поможет пролить свет на вашу проблему.