檢視原始碼 巨集 (Elixir v1.16.2)
用於處理 AST 和實作巨集的函式。
巨集是在編譯時接收 Elixir 的 AST 作為輸入,並傳回 Elixir 的 AST 作為輸出的建構。
這個模組中的許多函式存在,就是為了處理 Elixir AST,以遍歷、查詢和轉換它。
我們來看一個簡單的範例,說明函式和巨集之間的差異
defmodule Example do
defmacro macro_inspect(value) do
IO.inspect(value)
value
end
def fun_inspect(value) do
IO.inspect(value)
value
end
end
現在我們來試試看
import Example
macro_inspect(1)
#=> 1
#=> 1
fun_inspect(1)
#=> 1
#=> 1
到目前為止,它們的行為相同,因為我們傳遞一個整數作為引數。但我們來看一下當我們傳遞一個表達式時會發生什麼事
macro_inspect(1 + 2)
#=> {:+, [line: 3], [1, 2]}
#=> 3
fun_inspect(1 + 2)
#=> 3
#=> 3
巨集接收作為引數傳遞的程式碼表示,而函式接收作為引數傳遞的程式碼結果。巨集必須傳回程式碼表示的超集。請參閱 input/0
和 output/0
以取得更多資訊。
若要深入了解 Elixir 的 AST 以及如何以程式方式建構它們,請參閱 quote/2
。
評估程式碼
這個模組中的函式不會評估程式碼。事實上,從巨集中評估程式碼通常是一種反模式。對於程式碼評估,請參閱
Code
模組。
摘要
函數
將給定的字串轉換為 CamelCase 格式。
根據可能的 AST 位置對 atom
進行分類。
在 caller
中編譯時套用 mod
、function
和 args
。
將區域或遠端呼叫分解為其遠端部分(如果提供)、函數名稱和參數清單。
遞迴轉譯值,以便可以將其插入語法樹中。
接收 AST 節點並擴充它,直到無法再擴充為止。
使用給定的 env
擴充 ast
中的所有文字。
使用給定的 acc
和 fun
擴充 ast
中的所有文字。
接收 AST 節點並擴充一次。
使用 Macro.var/2
為給定數量的必要參數變數產生 AST 節點。
使用 Macro.unique_var/2
為給定數量的必要參數變數產生 AST 節點。
根據不同的來源格式檢查 atom
。
如果給定的名稱和元數是運算子,則傳回 true
。
傳回 ast
中節點的路徑,fun
為其傳回真值。
將 expr
導向給定 position
的 call_args
。
此函數的行為類似於 prewalk/2
,但對引號表達式執行深度優先、後序遍歷。
此函數的行為類似於 prewalk/3
,但使用累加器對引號表達式執行深度優先、後序遍歷。
傳回一個可列舉的物件,以深度優先、後序遍歷方式遍歷 ast
。
以深度優先、前序遍歷方式遍歷引號表達式。
以深度優先、前序遍歷方式遍歷引號表達式,並使用累加器。
傳回一個可列舉的物件,以深度優先、前序遍歷方式遍歷 ast
。
如果給定的引號表達式表示引號文字,則傳回 true
。
如果給定的名稱和元數是特殊形式,則傳回 true
。
在給定的 env
中擴充 module
給出的結構。
將給定的表達式 AST 轉換為字串。
將給定的表達式 AST 轉換為字串。
以深度優先方式遍歷引號表達式,並使用累加器。
將給定的參數轉換為使用底線斜線格式的字串。
取消轉義字串中的字元。
根據給定的對應取消轉義字串中的字元。
產生一個 AST 節點,表示由原子 var
和 context
給出的唯一變數。
將管線表達式分解成一個清單。
如果節點元資料包含一個值,則對該值套用給定的函式。
驗證給定的表達式是否為有效的引號表達式。
產生一個 AST 節點,表示由原子 var
和 context
給出的變數。
類型
@type captured_remote_function() :: (... -> any())
以 &Mod.fun/arity 格式擷取的遠端函式
巨集的輸入
@type metadata() :: keyword()
AST 元資料的關鍵字清單。
Elixir AST 中的元資料是值的關鍵字清單。任何鍵都可以使用,而編譯器的不同部分可能會使用不同的鍵。例如,巨集所接收的 AST 將永遠包含 :line
註解,而 quote/2
所發出的 AST 只有在提供 :line
選項時才會具有 :line
註解。
下列元資料鍵是公開的
:context
- 定義產生 AST 的內容。例如,quote/2
會將呼叫quote/2
的模組包含為內容。這通常用於區分一般程式碼和巨集或quote/2
產生的程式碼。:counter
- 用於變數衛生的變數計數器。在編譯器中,每個變數都透過name
和metadata[:counter]
或name
和context
的組合來識別。:from_brackets
- 用於判斷對Access.get/3
的呼叫是否來自方括號語法。:from_interpolation
- 用於判斷對Kernel.to_string/1
的呼叫是否來自內插。:generated
- 程式碼是否應視為由編譯器產生。這表示編譯器和 Dialyzer 等工具可能不會發出特定警告。:if_undefined
- 如何展開未定義的變數。如果您希望變數變成沒有警告的空元呼叫,請將其設定為:apply
,或:raise
:keep
-quote/2
使用選項location: :keep
來註解引述來源的檔案和行號。:line
- AST 節點的行號。請注意,引述程式碼會捨棄行資訊,但可透過:line
選項重新啟用。
以下的元資料金鑰由 Code.string_to_quoted/2
啟用
:closing
- 包含有關關閉配對的元資料,例如元組或映射中的}
,或帶有括號的函式呼叫中的關閉)
(當:token_metadata
為 true 時)。如果函式呼叫附加了 do-end 區塊,其元資料會出現在:do
和:end
元資料中:column
- AST 節點的欄位號碼(當:columns
為 true 時)。請注意,引述程式碼會始終捨棄欄位資訊。:delimiter
- 包含符號、字串和字元清單的開頭分隔符號,為字串(例如"{"
、"/"
、"'"
等):format
- 當原子定義為關鍵字時,設定為:keyword
:do
- 包含函數呼叫中do
位置的元資料,並帶有do
-end
區塊(當:token_metadata
為 true 時):end
- 包含函數呼叫中end
位置的元資料,並帶有do
-end
區塊(當:token_metadata
為 true 時):end_of_expression
- 表示表達式實際發生結束的時間(當:token_metadata
為 true 時)。這僅適用於__block__
的直接子節點,且為換行符號或;
字元的所在位置。__block__
的最後一個表達式沒有這個元資料。:indentation
- 符號 heredoc 的縮排
下列元資料金鑰為私人
:alias
- 用於別名衛生。:ambiguous_op
- 用於編譯器中改善的錯誤訊息。:imports
- 用於匯入衛生。:var
- 用於未定義變數的改善錯誤訊息。
不要依賴它們,因為它們可能會在語言的未來版本中變更或完全移除。它們通常由 quote/2
和編譯器使用,以提供衛生、更好的錯誤訊息等功能。
如果您在 AST 元資料中引入自訂金鑰,請務必在金鑰前面加上您的程式庫或應用程式的名稱,這樣它們才不會與編譯器未來可能引入的金鑰衝突。
@type output() :: output_expr() | {output(), output()} | [output()] | atom() | number() | binary() | captured_remote_function() | pid()
巨集的輸出
@type t() :: input()
抽象語法樹 (AST)
函數
將給定的字串轉換為 CamelCase 格式。
此函式設計用於將語言識別碼/代碼轉換為駝峰式大小寫,因此它屬於 Macro
模組。請勿將它用作將字串轉換為駝峰式大小寫的一般機制,因為它不支援 Unicode 或 Elixir 識別碼中無效的字元。
範例
iex> Macro.camelize("foo_bar")
"FooBar"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
如果存在大寫字元,它們不會以任何方式修改,作為保留縮寫的機制
iex> Macro.camelize("API.V1")
"API.V1"
iex> Macro.camelize("API_SPEC")
"API_SPEC"
@spec classify_atom(atom()) :: :alias | :identifier | :quoted | :unquoted
根據可能的 AST 位置對 atom
進行分類。
它會傳回下列原子之一
:alias
- 原子表示別名:identifier
- 原子可用作變數或區域函式呼叫 (以及作為未引用的原子):unquoted
- 原子可用於其未引用的形式,包括運算子及包含@
的原子:quoted
- 所有其他原子,只能用於其引用的形式
大多數運算子將會是 :unquoted
,例如 :+
,但有些例外會傳回 :quoted
,因為有歧義,例如 :"::"
。使用 operator?/2
來檢查給定的原子是否為運算子。
範例
iex> Macro.classify_atom(:foo)
:identifier
iex> Macro.classify_atom(Foo)
:alias
iex> Macro.classify_atom(:foo@bar)
:unquoted
iex> Macro.classify_atom(:+)
:unquoted
iex> Macro.classify_atom(:Foo)
:unquoted
iex> Macro.classify_atom(:"with spaces")
:quoted
在 caller
中編譯時套用 mod
、function
和 args
。
當您要在編譯時以程式方式呼叫巨集時,會使用這個。
@spec dbg(t(), t(), Macro.Env.t()) :: t()
Kernel.dbg/2
的預設後端。
此函式提供 Kernel.dbg/2
的預設後端。請參閱 Kernel.dbg/2
文件以取得更多資訊。
此函式
- 列印關於給定
env
的資訊 - 列印關於
code
及其傳回值 (使用opts
來檢查項目) 的資訊 - 傳回評估
code
所傳回的值
您可以直接呼叫此函式來建立回歸到此函式的 Kernel.dbg/2
後端。
此函式會在給定的 env
的內容為 :match
或 :guard
時引發錯誤。
將區域或遠端呼叫分解為其遠端部分(如果提供)、函數名稱和參數清單。
在提供無效的呼叫語法時,會傳回 :error
。
範例
iex> Macro.decompose_call(quote(do: foo))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo()))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo(1, 2, 3)))
{:foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: Elixir.M.foo(1, 2, 3)))
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: 42))
:error
iex> Macro.decompose_call(quote(do: {:foo, [], []}))
:error
遞迴轉譯值,以便可以將其插入語法樹中。
範例
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1
選項
:unquote
- 為 true 時,此函式會保留unquote/1
和unquote_splicing/1
陳述式未經過 escape,並在 escape 時有效地取消內容的引用。此選項僅在 escape 可能包含引號片段的 AST 時才有用。預設為 false。:prune_metadata
- 為 true 時,會移除已 escape 的 AST 節點的元資料。請注意,此選項會變更已 escape 的程式碼的語意,且僅應在 escape AST 時使用。預設為 false。舉例來說,
ExUnit
會儲存每個斷言的 AST,因此當斷言失敗時,我們可以向使用者顯示程式碼片段。沒有此選項時,每次編譯測試模組時,我們會取得模組位元組碼不同的 MD5,因為 AST 包含特定於編譯環境的元資料,例如計數器。透過修剪元資料,我們可以確保模組是確定性的,並減少ExUnit
需要保留的資料量。僅保留最少的元資料,例如:line
和:no_parens
。
與 quote/2
的比較
escape/2
函式有時會與 quote/2
混淆,因為上述範例在兩個函式下行為相同。關鍵差異最能從將要 escape 的值儲存在變數中時看出。
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> quote do: {:a, :b, :c}
{:{}, [], [:a, :b, :c]}
iex> value = {:a, :b, :c}
iex> Macro.escape(value)
{:{}, [], [:a, :b, :c]}
iex> quote do: value
{:value, [], __MODULE__}
iex> value = {:a, :b, :c}
iex> quote do: unquote(value)
{:a, :b, :c}
@spec expand(input(), Macro.Env.t()) :: output()
接收 AST 節點並擴充它,直到無法再擴充為止。
請注意,此函式不會遍歷 AST,只會展開根節點。
此函式在幕後使用 expand_once/2
。請查看以取得更多資訊和範例。
@spec expand_literals(input(), Macro.Env.t()) :: output()
使用給定的 env
擴充 ast
中的所有文字。
此函式主要用於移除 AST 節點的編譯時期相依性。在這種情況下,給定的環境通常會被操作,以表示一個函式
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
目前,AST 中唯一可擴充的文字節點是別名,因此此函式只會擴充別名(而且會在文字中的任何位置擴充)。
不過,在移除模組之間的編譯時期相依性時要小心。如果移除它們,但仍會在編譯時期呼叫模組,Elixir 將無法在模組變更時正確重新編譯模組。
使用給定的 acc
和 fun
擴充 ast
中的所有文字。
fun
會使用可擴充的 AST 節點和 acc
呼叫,且必須傳回一個包含 acc
的新節點。這是 expand_literals/2
的一般版本,它支援自訂擴充函式。請查看 expand_literals/2
的使用案例和陷阱。
@spec expand_once(input(), Macro.Env.t()) :: output()
接收 AST 節點並擴充一次。
會擴充以下內容
- 巨集(本機或遠端)
- 別名會擴充(如果可能)並傳回原子
- 編譯環境巨集 (
__CALLER__/0
、__DIR__/0
、__ENV__/0
和__MODULE__/0
) - 模組屬性讀取器 (
@foo
)
如果無法擴充表達式,它會傳回表達式本身。此函式不會遍歷 AST,只會擴充根節點。
expand_once/2
只會執行一次擴充。請查看 expand/2
以執行擴充,直到無法再擴充節點為止。
範例
在以下範例中,我們有一個巨集,它會產生一個模組,其中有一個名為 name_length
的函式,會傳回模組名稱的長度。此函式的值會在編譯時計算,而非在執行時。
考慮以下實作
defmacro defmodule_with_length(name, do: block) do
length = length(Atom.to_charlist(name))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
當像這樣呼叫時
defmodule_with_length My.Module do
def other_function, do: ...
end
編譯會失敗,因為 My.Module
在引用時不是一個原子,而是一個語法樹,如下所示
{:__aliases__, [], [:My, :Module]}
也就是說,我們需要將上述別名的節點擴充為一個原子,才能擷取其長度。擴充節點並不容易,因為我們也需要擴充呼叫者的別名。例如
alias MyHelpers, as: My
defmodule_with_length My.Module do
def other_function, do: ...
end
最後的模組名稱會是 MyHelpers.Module
,而不是 My.Module
。使用 Macro.expand/2
時,會考慮到這些別名。本機和遠端巨集也會被擴充。我們可以將上述巨集改寫為使用此函式,如下所示
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(Atom.to_charlist(expanded))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
@spec generate_arguments(0, context :: atom()) :: []
@spec generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...] when context: atom()
使用 Macro.var/2
為給定數量的必要參數變數產生 AST 節點。
請注意,引數並非唯一的。如果你稍後想要存取相同的變數,你可以使用相同的輸入呼叫此函式。使用 generate_unique_arguments/2
來產生無法覆寫的唯一引數。
範例
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}]
@spec generate_unique_arguments(0, context :: atom()) :: []
@spec generate_unique_arguments(pos_integer(), context) :: [ {atom(), [{:counter, integer()}], context}, ... ] when context: atom()
使用 Macro.unique_var/2
為給定數量的必要參數變數產生 AST 節點。
範例
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true
根據不同的來源格式檢查 atom
。
可以根據原子在 AST 中出現的三種不同格式來檢查原子:文字 (:literal
)、金鑰 (:key
) 或遠端呼叫的函式名稱 (:remote_call
)。
範例
作為文字
文字包括一般原子、引用原子、運算子、別名,以及特殊原子 nil
、true
和 false
。
iex> Macro.inspect_atom(:literal, nil)
"nil"
iex> Macro.inspect_atom(:literal, :foo)
":foo"
iex> Macro.inspect_atom(:literal, :<>)
":<>"
iex> Macro.inspect_atom(:literal, :Foo)
":Foo"
iex> Macro.inspect_atom(:literal, Foo.Bar)
"Foo.Bar"
iex> Macro.inspect_atom(:literal, :"with spaces")
":\"with spaces\""
做為一個鍵
檢查一個原子做為一個關鍵字清單或一個映射的鍵。
iex> Macro.inspect_atom(:key, :foo)
"foo:"
iex> Macro.inspect_atom(:key, :<>)
"<>:"
iex> Macro.inspect_atom(:key, :Foo)
"Foo:"
iex> Macro.inspect_atom(:key, :"with spaces")
"\"with spaces\":"
做為一個遠程呼叫
檢查一個原子做為一個遠程呼叫的函數名稱。
iex> Macro.inspect_atom(:remote_call, :foo)
"foo"
iex> Macro.inspect_atom(:remote_call, :<>)
"<>"
iex> Macro.inspect_atom(:remote_call, :Foo)
"\"Foo\""
iex> Macro.inspect_atom(:remote_call, :"with spaces")
"\"with spaces\""
如果給定的名稱和元數是運算子,則傳回 true
。
範例
iex> Macro.operator?(:not_an_operator, 3)
false
iex> Macro.operator?(:.., 0)
true
iex> Macro.operator?(:+, 1)
true
iex> Macro.operator?(:++, 2)
true
iex> Macro.operator?(:..//, 3)
true
@spec path(t(), (t() -> as_boolean(term()))) :: [t()] | nil
傳回 ast
中節點的路徑,fun
為其傳回真值。
路徑是一個清單,從 fun
回傳真值節點開始,接著是它的所有父節點。
如果 fun
只回傳假值,則回傳 nil
。
當你想要在 AST 中找到一個特定節點及其上下文,然後對它做某種斷言時,計算路徑可能是一個有效率的操作。
範例
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: [1, 2]), & &1 == 5)
nil
iex> Macro.path(quote(do: Foo.bar(3)), & &1 == 3)
[3, quote(do: Foo.bar(3))]
iex> Macro.path(quote(do: %{foo: [bar: :baz]}), & &1 == :baz)
[
:baz,
{:bar, :baz},
[bar: :baz],
{:foo, [bar: :baz]},
{:%{}, [], [foo: [bar: :baz]]}
]
將 expr
導向給定 position
的 call_args
。
這個函數可以用來實作類似 |>
的功能。例如,|>
本身是這樣實作的
defmacro left |> right do
Macro.pipe(left, right, 0)
end
expr
是表達式的 AST。 call_args
必須是 呼叫 的 AST,否則這個函數會引發錯誤。舉例來說,考慮管道運算子 |>/2
,它使用這個函數來建立管道。
即使表達式被管道到 AST 中,並不表示 AST 一定是有效的。例如,你可以將一個參數管道到 div/2
,實際上將它變成對 div/3
的呼叫,而 div/3
函數在預設情況下是不存在的。除非 div/3
函數在本地被定義,否則程式碼會引發錯誤。
此函數的行為類似於 prewalk/2
,但對引號表達式執行深度優先、後序遍歷。
此函數的行為類似於 prewalk/3
,但使用累加器對引號表達式執行深度優先、後序遍歷。
@spec postwalker(t()) :: Enumerable.t()
傳回一個可列舉的物件,以深度優先、後序遍歷方式遍歷 ast
。
範例
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.postwalker(ast), & &1)
[1, "abc", {:foo, [], [1, "abc"]}]
以深度優先、前序遍歷方式遍歷引號表達式。
回傳一個新的 AST,其中每個節點都是呼叫 fun
對應於 ast
的每個節點的結果。
範例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> new_ast = Macro.prewalk(ast, fn
...> {:+, meta, children} -> {:*, meta, children}
...> {:*, meta, children} -> {:+, meta, children}
...> other -> other
...> end)
iex> {:*, _, [5, {:+, _, [3, 7]}]} = new_ast
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
以深度優先、前序遍歷方式遍歷引號表達式,並使用累加器。
回傳一個二元組,其中第一個元素是一個新的 AST,其中每個節點都是呼叫 fun
對應於每個節點的結果,而第二個元素是最後的累加器。
範例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} = Macro.prewalk(ast, [], fn
...> {:+, meta, children}, acc -> {{:*, meta, children}, [:+ | acc]}
...> {:*, meta, children}, acc -> {{:+, meta, children}, [:* | acc]}
...> other, acc -> {other, acc}
...> end)
iex> {{:*, _, [5, {:+, _, [3, 7]}]}, [:*, :+]} = {new_ast, acc}
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
@spec prewalker(t()) :: Enumerable.t()
傳回一個可列舉的物件,以深度優先、前序遍歷方式遍歷 ast
。
範例
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.prewalker(ast), & &1)
[{:foo, [], [1, "abc"]}, 1, "abc"]
如果給定的引號表達式表示引號文字,則傳回 true
。
原子和數字永遠是文字。二進制、清單、元組、映射和結構只有在它們的所有條款都是文字時才是文字。
範例
iex> Macro.quoted_literal?(quote(do: "foo"))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1}))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1, :baz}))
true
iex> Macro.quoted_literal?(quote(do: %{foo: "bar"}))
true
iex> Macro.quoted_literal?(quote(do: %URI{path: "/"}))
true
iex> Macro.quoted_literal?(quote(do: URI.parse("/")))
false
iex> Macro.quoted_literal?(quote(do: {foo, var}))
false
如果給定的名稱和元數是特殊形式,則傳回 true
。
@spec struct!(module, Macro.Env.t()) :: %{ :__struct__ => module, optional(atom()) => any() } when module: module()
在給定的 env
中擴充 module
給出的結構。
當結構需要在編譯時展開,且被展開的結構可能已編譯或尚未編譯時,這項功能非常有用。此功能還能展開在正在編譯的模組下定義的結構。
如果結構不可用,它會引發 CompileError
。從 Elixir v1.12 開始,呼叫此功能也會新增對給定結構的匯出相依性。
將給定的表達式 AST 轉換為字串。
這是將 AST 轉換為字串的便利功能,它會捨棄原始程式碼的所有格式,並在 98 個字元周圍換行。請參閱 Code.quoted_to_algebra/2
,它是一個較低層級的功能,能更進一步控制格式。
範例
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3)))
"foo.bar(1, 2, 3)"
將給定的表達式 AST 轉換為字串。
給定的 fun
會在 AST 的每個節點呼叫,並帶有兩個參數:要列印的節點的 AST,以及該節點的字串表示形式。此功能的回傳值會用作該 AST 節點的最終字串表示形式。
此功能會捨棄原始程式碼的所有格式。
範例
Macro.to_string(quote(do: 1 + 2), fn
1, _string -> "one"
2, _string -> "two"
_ast, string -> string
end)
#=> "one + two"
@spec traverse(t(), any(), (t(), any() -> {t(), any()}), (t(), any() -> {t(), any()})) :: {t(), any()}
以深度優先方式遍歷引號表達式,並使用累加器。
傳回一個元組,其中第一個元素是新的 AST,第二個元素是最終累加器。新的 AST 是在預序階段對 ast
的每個節點呼叫 pre
,以及在後序階段呼叫 post
的結果。
範例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} =
...> Macro.traverse(
...> ast,
...> [],
...> fn
...> {:+, meta, children}, acc -> {{:-, meta, children}, [:- | acc]}
...> {:*, meta, children}, acc -> {{:/, meta, children}, [:/ | acc]}
...> other, acc -> {other, acc}
...> end,
...> fn
...> {:-, meta, children}, acc -> {{:min, meta, children}, [:min | acc]}
...> {:/, meta, children}, acc -> {{:max, meta, children}, [:max | acc]}
...> other, acc -> {other, acc}
...> end
...> )
iex> {:min, _, [5, {:max, _, [3, 7]}]} = new_ast
iex> [:min, :max, :/, :-] = acc
iex> Code.eval_quoted(new_ast)
{5, []}
將給定的參數轉換為使用底線斜線格式的字串。
參數必須是原子或字串。如果給定原子,假設為 Elixir 模組,因此會轉換為字串,然後處理。
此函數設計為使用底線斜線格式來格式化語言識別碼/代碼,這就是它屬於 Macro
模組的原因。請勿將其用作底線字串的一般機制,因為它不支援 Unicode 或 Elixir 識別碼中無效的字元。
範例
iex> Macro.underscore("FooBar")
"foo_bar"
iex> Macro.underscore("Foo.Bar")
"foo/bar"
iex> Macro.underscore(Foo.Bar)
"foo/bar"
一般而言,underscore
可以視為 camelize
的反向操作,然而,在某些情況下,格式化可能會遺失
iex> Macro.underscore("SAPExample")
"sap_example"
iex> Macro.camelize("sap_example")
"SapExample"
iex> Macro.camelize("hello_10")
"Hello10"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
取消轉義字串中的字元。
這是 Elixir 單引號和雙引號字串中預設使用的取消跳脫行為。請查看 unescape_string/2
,以取得如何自訂跳脫對應的資訊。
在此設定中,Elixir 會跳脫下列項目:\0
、\a
、\b
、\d
、\e
、\f
、\n
、\r
、\s
、\t
和 \v
。位元組可透過 \xNN
以十六進位表示,Unicode 碼點則可透過 \uNNNN
跳脫表示。
此函數通常用於 sigil 實作(例如 ~r
、~s
等),它會接收原始的未跳脫字串,且可用於任何需要模擬 Elixir 如何剖析字串的地方。
範例
iex> Macro.unescape_string("example\\n")
"example\n"
在上述範例中,我們傳遞一個跳脫 \n
的字串,並傳回未跳脫的版本。
@spec unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
根據給定的對應取消轉義字串中的字元。
如果您想使用與 Elixir 單引號和雙引號字串相同的對應,請查看 unescape_string/1
。
對應函數
對應函數接收一個整數,表示它想要取消跳脫的字元的碼點。另外還有特殊原子 :newline
、:unicode
和 :hex
,分別控制換行、Unicode 和跳脫。
以下是 Elixir 實作的預設對應函數
def unescape_map(:newline), do: true
def unescape_map(:unicode), do: true
def unescape_map(:hex), do: true
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
如果 unescape_map/1
函數傳回 false
,則不會跳脫字元,且反斜線會保留在字串中。
範例
使用上面定義的 unescape_map/1
函式很簡單
Macro.unescape_string("example\\n", &unescape_map(&1))
@spec unique_var(var, context) :: {var, [{:counter, integer()}], context} when var: atom(), context: atom()
產生一個 AST 節點,表示由原子 var
和 context
給出的唯一變數。
使用相同的參數呼叫此函式將會產生另一個變數,並具有其自己的唯一計數器。請參閱 var/2
以取得替代方案。
範例
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true
將管線表達式分解成一個清單。
管線 (一系列 |>/2
應用) 的 AST 與一系列二元運算子或函式應用的 AST 類似:最上層的表達式是最右邊的 :|>
(這是最後一個要執行的),而其左手邊和右手邊則為其參數
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]}
在上面的範例中,|>/2
管線是最右邊的管線;arg1
是 100 |> div(5)
的 AST,而 arg2
是 div(2)
的 AST。
通常會需要將此類管線的 AST 作為函式應用清單。此函式執行這項工作
Macro.unpipe(quote do: 100 |> div(5) |> div(2))
#=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]
我們會取得一個直接遵循管線的清單:首先是 100
,接著是 div(5)
(更精確地說,是其 AST),然後是 div(2)
。在組成的第二個元素中,0
是管線中前一個元素在目前函式應用中的位置:{{:div, [], [5]}, 0}
表示前一個元素 (100
) 將會插入為 div/2
函式的第 0 個 (第一個) 參數,因此該函式的 AST 將會變成 {:div, [], [100, 5]}
(div(100, 5)
)。
如果節點元資料包含一個值,則對該值套用給定的函式。
這通常會在與 Macro.prewalk/2
搭配使用時很有用,用於移除表達式中的資訊,例如行數和衛生計數器,以進行儲存或比較。
範例
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}
驗證給定的表達式是否為有效的引號表達式。
請查看類型 Macro.t/0
以取得有效引號表達式的完整規格。
如果表達式有效,則會傳回 :ok
。否則,會傳回一個格式為 {:error, remainder}
的組成,其中 remainder
是引號表達式中無效的部分。
範例
iex> Macro.validate({:two_element, :tuple})
:ok
iex> Macro.validate({:three, :element, :tuple})
{:error, {:three, :element, :tuple}}
iex> Macro.validate([1, 2, 3])
:ok
iex> Macro.validate([1, 2, 3, {4}])
{:error, {4}}
產生一個 AST 節點,表示由原子 var
和 context
給出的變數。
請注意,此變數並非唯一的。如果您稍後想要存取同一個變數,您可以再次使用相同的參數呼叫 var/2
。請使用 unique_var/2
來產生一個無法覆寫的唯一變數。
範例
為了建立變數,預期會有一個內容。大部分時間,為了保持衛生,內容必須是 __MODULE__/0
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}
不過,如果需要存取使用者變數,可以提供 nil
iex> Macro.var(:foo, nil)
{:foo, [], nil}