在 Crystal 宏中为新变量赋值

Assigning a value to a new variable in a Crystal macro

我想在宏中创建一个新变量。我的代码可以减少到:

macro test()
  %p = "a = 3"
  %p
end

test()
puts(a)

但我收到一个错误 undefined local variable or method 'a'

我试图将 %p 包装在 {{ }}{% %} 中,但它无法编译 ("unexpected token: %")

编辑

这是一些背景信息。

我经常不得不使用带有一些公共字段的两个命名元组,然后将其转换为 JSON 并发送给不同的客户端。

a = {x: xx, y: yy, w: ww}
b = {x: xx, y: yy, z: zz}

目前我正在重复写这两篇文章。 ww、xx、yy、zz 的值在编译时未知。我想用在编译时调用进行合并的宏来替换它。

我想出了以下代码来创建我当前手动输入的代码行:

macro merge(common, aOnly, bOnly)
  # Start, middle and end parts of target instructions
  %p1a = "a = {"
  %p1b = "b = {"
  %p2 = ""
  %p2a = ""
  %p2b = ""
  %p3 = "}"

  # Creating the middle part
  {% for key, value in common %}
    %p2 = %p2 + "{{key.id}}" + ": " + "{{value}}" + ','
  {% end %}
  %p2a = %p2
  {% for key, value in aOnly %}
    %p2a = %p2a + "{{key.id}}" + ": " + "{{value}}" + ','
  {% end %}
  %p2b = %p2
  {% for key, value in bOnly %}
    %p2b = %p2b + "{{key.id}}" + ": " + "{{value}}" + ','
  {% end %}

  # Removing unneeded comma
  %p2a = %p2a.chomp(",")
  %p2b = %p2b.chomp(",")

  #  Display
  puts(%p1a + %p2a + %p3)
  puts(%p1b + %p2b + %p3)

  # Definition of new variables
  %p1a + %p2a + %p3
  %p1b + %p2b + %p3
end

merge({x: xx, y: yy}, {w: ww}, {z: zz})

这里的主要问题是对%names的误解。这不是使指令编译时间的构造。写 %a = 5 和写 guaranteed_unique_name = 5 是一样的——这是一种避免干扰可能存在于同一范围内的其他变量的方法,但它仍然是正常的运行时变量赋值。

构造字符串然后在编译时输出它们不是使用宏的可行方法,而且它几乎总是不可能结束,因为您不能将不同的值重新分配给宏变量。但这里有一个例子,如果它确实有效,它可能会是什么样子,仍然尝试将你的方法与字符串一起使用:

macro merge(common, aOnly, bOnly)
  # Start, middle and end parts of target instructions
  {%
    p1a = "a = {"
    p1b = "b = {"
    p2 = ""
    p2a = ""
    p2b = ""
    p3 = "}"
  %}

  # Creating the middle part
  {% for key, value in common %}
    {% p2 = p2 + "#{key.id}: #{value}," %}
  {% end %}
  {% p2a = p2 %}
  {% for key, value in aOnly %}
    {% p2a += "#{key.id}: #{value}," %}
  {% end %}
  {% p2b = p2 %}
  {% for key, value in bOnly %}
    {% p2b += "#{key.id}: #{value}," %}
  {% end %}

  # Removing unneeded comma
  {% p2a = p2a.chomp(",") %}
  {% p2b = p2b.chomp(",") %}

  # Definition of new variables
  {{(p1a + p2a + p3).id}}
  {{(p1b + p2b + p3).id}}
end

merge({x: xx, y: yy}, {w: ww}, {z: zz})

现在我将使用更常用的结构重写您的示例:

macro merge(common, a_only, b_only)
  a = {
    {% for key, value in common %}
      {{key}}: {{value}},
    {% end %}
    {% for key, value in a_only %}
      {{key}}: {{value}},
    {% end %}
  }

  b = {
    {% for key, value in common %}
      {{key}}: {{value}},
    {% end %}
    {% for key, value in b_only %}
      {{key}}: {{value}},
    {% end %}
  }
end

merge({x: "a", y: 5}, {w: "b"}, {z: 7})

pp a, b

如您所见,放在宏 body 中的普通代码是运行时代码,而 {% %}{{ }} 中的代码是在编译时执行的。您可以制作编译时循环并将它们与普通代码行交织在一起,以自然地创建复杂代码。

希望这回答了问题标题"Assigning a value to a new variable in a Crystal macro":将一些东西赋值给运行时变量,你做的事情与在宏中输出任何运行时代码一样——你只需把它写出来,比如a = 5,因为默认情况下宏输出运行时代码,并且只有 {%{{ 分隔编译时构造。


为了回复评论,这里可能是一个更惯用的版本,它甚至说明了 temporary/unique 变量名。它使用它们来创建元组,然后宏执行的结果是一个可以方便地解包的元组,而不是依赖副作用,这种情况很少发生。

macro merge(common, a_only, b_only)
  %a = {
    {% for key, value in common %}
      {{key}}: {{value}},
    {% end %}
    {% for key, value in a_only %}
      {{key}}: {{value}},
    {% end %}
  }

  %b = {
    {% for key, value in common %}
      {{key}}: {{value}},
    {% end %}
    {% for key, value in b_only %}
      {{key}}: {{value}},
    {% end %}
  }

  { %a, %b }
end

first, second = merge({x: "a", y: 5}, {w: "b"}, {z: 7})

puts first