要修复“无效的引用表达式”,您可以像这样使用Macro.escape/1:
Enum.any?(unquote(Macro.escape([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]])), &(unquote(char) in &1))
但这会引发另一个错误:
** (CompileError) a.exs:10: invalid expression in guard
expanding macro: Main.is_atom_literal/1
a.exs:10: Main.test/0
这是因为您试图在警卫中调用Enum.any?/2,这是不允许的。
幸运的是,有一个解决方法:只需使用or 连接所有表达式。这可以使用Enum.reduce/3 来完成:
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
这段代码的作用是将is_atom_literal(c)转换成:
false or c in %{__struct__: Range, first: 97, last: 122} or c in %{__struct__: Range, first: 65, last: 90} or c in %{__struct__: Range, first: 48, last: 57} or c in '_-'
这是一个有效的保护表达式,因为 Elixir 后来将in 用于范围和列表的糖分变成更简单的语句(类似于c >= 97 and c <= 122 or c >= 65 and c <= 90 or ...)。
代码仍然失败,因为您的输入是 'b',而宏需要一个字符。将'b' 更改为?b 有效:
defmodule Main do
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
def test do
c = ?b
case c do
c when is_atom_literal(c) ->
:ok
end
end
end
IO.inspect Main.test
输出:
:ok