2016-10-29 64 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