要解決「無效引用表達」你可以使用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