Состав списка выписок

Мне нужно разделить список выписок на несколько частей, например так:

import macros

macro test: stmt =
    var first = quote do:
        var x = 1
    var second = quote do:
        echo x
    result = newStmtList()
    first.copyChildrenTo(result)
    second.copyChildrenTo(result)

    echo result.repr

test

Но компилятор говорит мне это:

[..]

  var x = 1
  echo x
minimalist.nim(14, 0) Info: instantiation from here
minimalist.nim(7, 13) Error: undeclared identifier: 'x'
          echo x

Это очень запутанно, учитывая, что x объявлен в списке узлов. Как мне сделать это правильно? (в случае, если это не очевидно, мне нужно разделить AST на несколько частей по другим причинам)

2 ответа

Решение

По умолчанию Nim будет применять правила гигиены макросов для кода внутри блоков в кавычках. Это означает, что имена символов, введенные в одном блоке, не будут видны в другом. Вы можете обойти это, введя переменную для общего символа следующим образом:

import macros

macro test: stmt =
  var x = genSym()

  var first = quote do:
      var `x` = 1

  var second = quote do:
      echo `x`

  result = newStmtList()
  first.copyChildrenTo(result)
  second.copyChildrenTo(result)

test

Под капотом цитата питается от getAst операция применяется к незапятнанному шаблону. Если вы используете механизм более низкого уровня самостоятельно, также возможно отключить гигиеническое поведение цитируемых шаблонов, например так:

import macros

macro test: stmt =
  template first {.dirty.} =
    var x = 1

  template second {.dirty.} =
    echo x

  result = newStmtList()
  getAst(first()).copyChildrenTo(result)
  getAst(second()).copyChildrenTo(result)

test

Это даст те же результаты. Имейте в виду, что quote в конечном итоге получит флаг, который контролирует грязность цитируемого кода.

Если вы сравните AST, то увидите, что происходит не так:

dumpTree:
  var x = 1
  echo x

Это печатает:

StmtList
  VarSection
    IdentDefs
      Ident !"x"
      Empty
      IntLit 1
  Command
    Ident !"echo"
    Ident !"x"

Тогда в вашем примере:

macro test: stmt =
    var first = quote do:
        var x = 1
    var second = quote do:
        echo x

    result = newStmtList()
    first.copyChildrenTo(result)
    second.copyChildrenTo(result)

    echo result.treeRepr

test

Это печатает:

StmtList
  VarSection
    IdentDefs
      Sym "x"
      Empty
      IntLit 1
  Command
    Sym "echo"
    Ident !"x"

Заметили разницу? Однажды "х" является Ident, другой раз Sym. Символы не должны были быть найдены, возможно, это следует изменить в компиляторе. Но сейчас вы можете помочь себе и заменить все Syms на Ident снова, чтобы вызвать новый поиск:

import macros

proc symsToIdents(n): PNimrodNode {.compiletime.} =
    if n.kind == nnkSym:
        return newIdentNode($n)

    result = n
    for i in 0 .. <result.len:
      result[i] = symsToIdents(n[i])

macro test: stmt =
    var first = quote do:
        var x = 1
    var second = quote do:
        echo x

    result = newStmtList()
    first.copyChildrenTo(result)
    second.copyChildrenTo(result)
    result = symsToIdents(result)

test

Изменить: сейчас сообщается как возможная ошибка: https://github.com/nim-lang/Nim/issues/1843

Другие вопросы по тегам