Metastatic (Metastatic v0.20.3)
View SourceMetastatic - Cross-language code analysis via unified MetaAST.
Main API
The primary entry points for working with Metastatic:
quote/2- Convert source code to MetaASTunquote/2- Convert MetaAST back to source codeprewalk/2,prewalk/3- Pre-order AST traversalpostwalk/2,postwalk/3- Post-order AST traversaltraverse/4- Full pre+post AST traversal with accumulatorpath/2- Find path from root to matching nodeto_string/1- Human-readable AST representationdecompose_call/1- Decompose function call nodesvalidate/1- Validate AST structure with diagnosticsunpipe/1- Decompose pipe chainspipe_into/3- Inject expression into function callliteral?/1- Check if AST is purely literaloperator?/1- Check if node is an operatorprewalker/1,postwalker/1- Lazy enumerable traversalsunique_var/1- Generate unique variable nodes
Examples
# Parse Python code to MetaAST
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :python)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
# Convert MetaAST back to Python source
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "x + 5"}
# Round-trip demonstration
iex> {:ok, my_ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, source} = Metastatic.unquote(my_ast, :python)
iex> source
"x + 5"Cross-Language Translation
Since MetaAST is language-independent, you can parse in one language and generate in another:
# Parse from Python, generate Elixir code (same MetaAST!)
iex> {:ok, py_ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, _source} = Metastatic.unquote(py_ast, :elixir)
iex> true
true
Summary
Functions
Decomposes a function call into {name, args} or :error.
Returns true if the MetaAST represents a purely literal value.
Returns true if the node is a binary or unary operator.
Returns the path to the node for which fun returns a truthy value.
Pipes expr into a function call at the given position.
Performs a depth-first, post-order traversal of the MetaAST (no accumulator).
Performs a depth-first, post-order traversal with accumulator.
Returns an enumerable that lazily traverses the MetaAST in post-order.
Performs a depth-first, pre-order traversal of the MetaAST (no accumulator).
Performs a depth-first, pre-order traversal with accumulator.
Returns an enumerable that lazily traverses the MetaAST in pre-order.
Convert source code to MetaAST.
Converts a MetaAST to a human-readable string.
Traverse a MetaAST, applying pre and post functions.
Generates a unique variable MetaAST node.
Breaks a pipe chain into a flat list of {ast, position} tuples.
Convert MetaAST to source code.
Validates AST structure, returning :ok or {:error, reason}.
Types
@type language() :: :elixir | :erlang | :ruby | :haskell | :python
@type meta_ast() :: Metastatic.AST.meta_ast()
Functions
Decomposes a function call into {name, args} or :error.
Delegates to Metastatic.AST.decompose_call/1.
Examples
iex> call = {:function_call, [name: "add"], [{:variable, [], "x"}, {:variable, [], "y"}]}
iex> Metastatic.decompose_call(call)
{"add", [{:variable, [], "x"}, {:variable, [], "y"}]}
iex> Metastatic.decompose_call({:literal, [subtype: :integer], 42})
:error
Returns true if the MetaAST represents a purely literal value.
Delegates to Metastatic.AST.literal?/1.
Examples
iex> Metastatic.literal?({:literal, [subtype: :integer], 42})
true
iex> Metastatic.literal?({:variable, [], "x"})
false
Returns true if the node is a binary or unary operator.
Delegates to Metastatic.AST.operator?/1.
Examples
iex> Metastatic.operator?({:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 1}]})
true
iex> Metastatic.operator?({:literal, [subtype: :integer], 42})
false
Returns the path to the node for which fun returns a truthy value.
The path is a list starting with the matched node, followed by its
parents up to the root. Returns nil if no match found.
Delegates to Metastatic.AST.path/2.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:variable, [], "x"}, {:literal, [subtype: :integer], 42}]}
iex> path = Metastatic.path(ast, fn
...> {:literal, _, 42} -> true
...> _ -> false
...> end)
iex> Enum.map(path, &Metastatic.AST.type/1)
[:literal, :binary_op]
Pipes expr into a function call at the given position.
Delegates to Metastatic.AST.pipe_into/3.
Examples
iex> expr = {:variable, [], "x"}
iex> call = {:function_call, [name: "foo"], [{:literal, [subtype: :integer], 1}]}
iex> Metastatic.pipe_into(expr, call, 0)
{:function_call, [name: "foo"], [{:variable, [], "x"}, {:literal, [subtype: :integer], 1}]}
Performs a depth-first, post-order traversal of the MetaAST (no accumulator).
Delegates to Metastatic.AST.postwalk/2.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> Metastatic.postwalk(ast, fn
...> {:literal, meta, value} when is_integer(value) -> {:literal, meta, value * 10}
...> node -> node
...> end)
{:binary_op, [category: :arithmetic, operator: :+],
[{:literal, [subtype: :integer], 10}, {:literal, [subtype: :integer], 20}]}
Performs a depth-first, post-order traversal with accumulator.
Delegates to Metastatic.AST.postwalk/3.
Returns an enumerable that lazily traverses the MetaAST in post-order.
Delegates to Metastatic.AST.postwalker/1.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> ast |> Metastatic.postwalker() |> Enum.map(&Metastatic.AST.type/1)
[:literal, :literal, :binary_op]
Performs a depth-first, pre-order traversal of the MetaAST (no accumulator).
Delegates to Metastatic.AST.prewalk/2.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> Metastatic.prewalk(ast, fn
...> {:literal, meta, value} when is_integer(value) -> {:literal, meta, value * 10}
...> node -> node
...> end)
{:binary_op, [category: :arithmetic, operator: :+],
[{:literal, [subtype: :integer], 10}, {:literal, [subtype: :integer], 20}]}
Performs a depth-first, pre-order traversal with accumulator.
Delegates to Metastatic.AST.prewalk/3.
Returns an enumerable that lazily traverses the MetaAST in pre-order.
Delegates to Metastatic.AST.prewalker/1.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> ast |> Metastatic.prewalker() |> Enum.map(&Metastatic.AST.type/1)
[:binary_op, :literal, :literal]
Convert source code to MetaAST.
Performs Source → M1 → M2 transformation, returning just the MetaAST without the Document wrapper.
Parameters
code- Source code stringlanguage- Language atom (:python,:elixir,:ruby,:erlang,:haskell)
Returns
{:ok, meta_ast}- Successfully parsed and abstracted to M2{:error, reason}- Parsing or abstraction failed
Examples
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :python)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :elixir)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
iex> {:ok, {:list, _, items}} = Metastatic.quote("[1, 2, 3]", :python)
iex> length(items) == 3
true
Converts a MetaAST to a human-readable string.
Delegates to Metastatic.AST.to_string/1.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.to_string(ast)
"x + 5"
Traverse a MetaAST, applying pre and post functions.
Delegates to Metastatic.AST.traverse/4. See that module for full documentation.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> {_ast, count} = Metastatic.traverse(ast, 0, fn node, acc -> {node, acc + 1} end, fn node, acc -> {node, acc} end)
iex> count
3
Generates a unique variable MetaAST node.
Delegates to Metastatic.AST.unique_var/1.
Examples
iex> {:variable, _, name} = Metastatic.unique_var("tmp")
iex> String.starts_with?(name, "tmp_")
true
Breaks a pipe chain into a flat list of {ast, position} tuples.
Delegates to Metastatic.AST.unpipe/1.
Convert MetaAST to source code.
Performs M2 → M1 → Source transformation, generating source code in the specified target language.
Parameters
ast- MetaAST structure (M2 level)language- Target language atom (:python,:elixir,:ruby,:erlang,:haskell)
Returns
{:ok, source}- Successfully generated source code{:error, reason}- Generation failed
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "x + 5"}
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :elixir)
{:ok, "x + 5"}
iex> ast = {:literal, [subtype: :integer], 42}
iex> Metastatic.unquote(ast, :python)
{:ok, "42"}
iex> ast = {:list, [], [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "[1, 2]"}Cross-Language Translation
Since MetaAST is language-independent, you can parse from one language and generate in another:
iex> {:ok, ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, source} = Metastatic.unquote(ast, :elixir)
iex> source
"x + 5"
iex> {:ok, ast} = Metastatic.quote("42", :ruby)
iex> {:ok, source} = Metastatic.unquote(ast, :python)
iex> source
"42"
Validates AST structure, returning :ok or {:error, reason}.
Delegates to Metastatic.AST.validate/1.
Examples
iex> Metastatic.validate({:literal, [subtype: :integer], 42})
:ok
iex> Metastatic.validate("not an ast")
{:error, {:not_an_ast_node, "not an ast"}}