要解决“无效引用表达”你可以使用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 '_-'
这是一个有效后卫表达药剂后desugars 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
非常好,伟大工程,谢谢。后续问题:你能想出一个更好的方法来检查单个字符是否是该列表中的任何字符?现在有点乱,不得不减少它。 – jeboysteven
如果只有这4个范围需要检查,我可能只需在宏中写入扩展形式:'quote do:unquote(c)> = 65并且取消引用(c)<= 90或...'。 – Dogbert
事实上,你可以做得更好:'quote do:unquote(c)in?a ..?z或unquote(c)in?A ..?Z or ...'。 – Dogbert