2016-10-29 89 views
0

我想知道是否有可能擴大宏的範圍。在宏中擴大範圍

目前,當我嘗試這種代碼:

defmodule Main do 
    defmacro is_atom_literal(char) do 
    quote do: 
     Enum.any?(unquote([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]), &(unquote(char) in &1)) 
    end 

    def test do 
    c = 'b' 
    case c do 
     c when is_atom_literal(c) -> 
     :ok 
    end 
    end 
end 

Main.test 

我得到的錯誤​​3210。是否有可能使這個想法有效?

回答

1

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

非常好,偉大工程,謝謝。後續問題:你能想出一個更好的方法來檢查單個字符是否是該列表中的任何字符?現在有點亂,不得不減少它。 – jeboysteven

+0

如果只有這4個範圍需要檢查,我可能只需在宏中寫入擴展形式:'quote do:unquote(c)> = 65並且取消引用(c)<= 90或...'。 – Dogbert

+1

事實上,你可以做得更好:'quote do:unquote(c)in?a ..?z或unquote(c)in?A ..?Z or ...'。 – Dogbert