为Crystal宏中的新变量赋值

时间:2017-02-18 12:55:40

标签: macros crystal-lang

我想在宏中创建一个新变量。我的代码可以简化为:

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})

1 个答案:

答案 0 :(得分:3)

这里的主要问题是对%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

正如您所看到的,放在宏体中的普通代码是运行时代码,而{% %}{{ }}中的内容在编译时执行。您可以编写时间循环并将它们与普通代码行交织在一起,以自然地创建复杂的代码。

希望能回答问题标题“为Crystal宏中的新变量赋值”:为运行时变量分配一些内容,就像在宏中输出任何运行时代码一样 - 你只需写出来与a = 5类似,因为默认情况下宏输出运行时代码,并且只有{%{{分隔编译时构造。

要回复评论,这里可能是一个更为惯用的版本,它甚至可以说明临时/唯一的变量名称。它使用它们来创建元组,然后宏执行的结果是那些可以方便地解压缩的元组,而不是依赖于副作用,这很少发生。

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