Packages
metastatic
0.1.2
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/adapters/elixir/from_meta.ex
defmodule Metastatic.Adapters.Elixir.FromMeta do
@moduledoc """
Transform MetaAST (M2) back to Elixir AST (M1).
This module implements the reification function ρ_Elixir that instantiates
meta-level representations back into Elixir-specific AST structures.
## Transformation Strategy
The transformation reverses the abstraction performed by `ToMeta`, using
metadata to restore M1-specific information when needed.
## Round-Trip Fidelity
The transformation aims for high fidelity:
- Metadata preserves line numbers, contexts, and formatting hints
- Variable contexts are restored from metadata
- Special forms are reconstructed from metadata markers
## Default Values
When metadata is absent or incomplete:
- Line number defaults to 0
- Variable context defaults to `Elixir`
- Empty metadata keyword list `[]` is used
"""
@doc """
Transform MetaAST back to Elixir AST.
Returns `{:ok, elixir_ast}` on success or `{:error, reason}` on failure.
## Examples
iex> transform({:literal, :integer, 42}, %{})
{:ok, 42}
iex> transform({:variable, "x"}, %{context: Elixir})
{:ok, {:x, [], Elixir}}
iex> transform({:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}, %{})
{:ok, {:+, [], [{:x, [], Elixir}, 5]}}
"""
@spec transform(term(), map()) :: {:ok, term()} | {:error, String.t()}
# Literals - M2.1 Core Layer
def transform({:literal, :integer, value}, _metadata) do
{:ok, value}
end
def transform({:literal, :float, value}, _metadata) do
{:ok, value}
end
def transform({:literal, :string, value}, _metadata) do
{:ok, value}
end
def transform({:literal, :boolean, value}, _metadata) do
{:ok, value}
end
def transform({:literal, :null, nil}, _metadata) do
{:ok, nil}
end
def transform({:literal, :symbol, atom}, _metadata) do
{:ok, atom}
end
def transform({:literal, :collection, items}, metadata) do
case transform_list(items, metadata) do
{:ok, transformed_items} ->
# Return as list literal
{:ok, transformed_items}
error ->
error
end
end
# Variables - M2.1 Core Layer
def transform({:variable, name}, metadata) when is_binary(name) do
var_atom = String.to_atom(name)
# Default context depends on caller - use what's in metadata or nil
context = Map.get(metadata, :context, nil)
elixir_meta = Map.get(metadata, :elixir_meta, [])
{:ok, {var_atom, elixir_meta, context}}
end
# Binary Operators - M2.1 Core Layer
def transform({:binary_op, _category, op, left, right}, metadata) do
with {:ok, left_ex} <- transform(left, metadata),
{:ok, right_ex} <- transform(right, metadata) do
{:ok, {op, [], [left_ex, right_ex]}}
end
end
# Unary Operators - M2.1 Core Layer
def transform({:unary_op, _category, op, operand}, metadata) do
with {:ok, operand_ex} <- transform(operand, metadata) do
{:ok, {op, [], [operand_ex]}}
end
end
# Function Calls - M2.1 Core Layer
def transform({:function_call, name, args}, metadata) do
with {:ok, args_ex} <- transform_list(args, metadata) do
# Check if this is a remote call (Module.function)
case String.split(name, ".") do
[single_name] ->
# Local call
func_atom = String.to_atom(single_name)
{:ok, {func_atom, [], args_ex}}
parts when length(parts) > 1 ->
# Remote call
{func_name, module_parts} = List.pop_at(parts, -1)
module_ast = build_module_alias(module_parts)
func_atom = String.to_atom(func_name)
{:ok, {{:., [], [module_ast, func_atom]}, [], args_ex}}
end
end
end
# Conditionals - M2.1 Core Layer
def transform({:conditional, condition, then_branch, else_branch}, metadata) do
with {:ok, cond_ex} <- transform(condition, metadata),
{:ok, then_ex} <- transform(then_branch, metadata),
{:ok, else_ex} <- transform_or_keep_nil(else_branch, metadata) do
# Check if this was originally an unless
case Map.get(metadata, :original_form) do
:unless ->
# Reconstruct unless (need to undo the negation)
actual_cond =
case cond_ex do
{:not, [], [inner_cond]} -> inner_cond
_ -> {:not, [], [cond_ex]}
end
clauses = if else_ex, do: [do: then_ex, else: else_ex], else: [do: then_ex]
{:ok, {:unless, [], [actual_cond, clauses]}}
_ ->
# Regular if
clauses = if else_ex, do: [do: then_ex, else: else_ex], else: [do: then_ex]
{:ok, {:if, [], [cond_ex, clauses]}}
end
end
end
# Blocks - M2.1 Core Layer
def transform({:block, expressions}, metadata) do
with {:ok, exprs_ex} <- transform_list(expressions, metadata) do
case exprs_ex do
[] -> {:ok, nil}
[single] -> {:ok, single}
multiple -> {:ok, {:__block__, [], multiple}}
end
end
end
# Inline Match (=) - M2.1 Core Layer
# Reconstruct Elixir pattern matching syntax
def transform({:inline_match, pattern, value}, metadata) do
pattern_metadata = Map.get(metadata, :pattern_metadata, %{})
value_metadata = Map.get(metadata, :value_metadata, %{})
elixir_meta = Map.get(metadata, :elixir_meta, [])
with {:ok, pattern_ex} <- transform_pattern_to_elixir(pattern, pattern_metadata),
{:ok, value_ex} <- transform(value, value_metadata) do
{:ok, {:=, elixir_meta, [pattern_ex, value_ex]}}
end
end
# Early Returns - M2.1 Core Layer
def transform({:early_return, kind, value}, metadata) do
with {:ok, value_ex} <- transform_or_keep_nil(value, metadata) do
# Elixir doesn't have direct return/break/continue
# These would be represented as throws or special constructs
case kind do
:return ->
{:ok, {:throw, [], [{:return, value_ex}]}}
:break ->
{:ok, {:throw, [], [:break]}}
:continue ->
{:ok, {:throw, [], [:continue]}}
end
end
end
# Pattern Matching - M2.2 Extended Layer
def transform({:pattern_match, scrutinee, arms}, metadata) do
with {:ok, scrutinee_ex} <- transform(scrutinee, metadata),
{:ok, arms_ex} <- transform_match_arms(arms, metadata) do
{:ok, {:case, [], [scrutinee_ex, [do: arms_ex]]}}
end
end
# Anonymous Functions - M2.2 Extended Layer
def transform({:lambda, params, body}, metadata) do
with {:ok, params_ex} <- transform_params(params, metadata),
{:ok, body_ex} <- transform(body, metadata) do
clause = {:->, [], [params_ex, body_ex]}
{:ok, {:fn, [], [clause]}}
end
end
# Collection Operations - M2.2 Extended Layer
def transform({:collection_op, :map, fun, collection}, metadata) do
with {:ok, collection_ex} <- transform(collection, metadata),
{:ok, fun_ex} <- transform(fun, metadata) do
# Check if this was originally a comprehension
case Map.get(metadata, :original_form) do
:comprehension ->
# Transform back to for comprehension
transform_to_comprehension(:map, fun_ex, collection_ex)
_ ->
# Regular Enum.map
enum_module = {:__aliases__, [], [:Enum]}
{:ok, {{:., [], [enum_module, :map]}, [], [collection_ex, fun_ex]}}
end
end
end
def transform({:collection_op, :filter, fun, collection}, metadata) do
with {:ok, collection_ex} <- transform(collection, metadata),
{:ok, fun_ex} <- transform(fun, metadata) do
enum_module = {:__aliases__, [], [:Enum]}
{:ok, {{:., [], [enum_module, :filter]}, [], [collection_ex, fun_ex]}}
end
end
def transform({:collection_op, :reduce, fun, collection, initial}, metadata) do
with {:ok, collection_ex} <- transform(collection, metadata),
{:ok, initial_ex} <- transform(initial, metadata),
{:ok, fun_ex} <- transform(fun, metadata) do
enum_module = {:__aliases__, [], [:Enum]}
{:ok, {{:., [], [enum_module, :reduce]}, [], [collection_ex, initial_ex, fun_ex]}}
end
end
# Language-Specific - M2.3 Native Layer
def transform({:language_specific, :elixir, native_ast, _hint}, _metadata) do
# Return the native Elixir AST as-is
{:ok, native_ast}
end
def transform({:language_specific, other_lang, _ast, _hint}, _metadata) do
{:error, "Cannot reify #{other_lang} language-specific construct to Elixir"}
end
# Tuples - Used in patterns and values
def transform({:tuple, elements}, metadata) when is_list(elements) do
with {:ok, elements_ex} <- transform_list(elements, metadata) do
case elements_ex do
[] ->
{:ok, {:{}, [], []}}
[e1, e2] ->
# Two-element tuple uses shorthand notation
{:ok, {e1, e2}}
_ ->
# Three or more elements use explicit tuple syntax
{:ok, {:{}, [], elements_ex}}
end
end
end
# Pin operator - Used in patterns
def transform({:pin, var}, metadata) do
with {:ok, var_ex} <- transform(var, metadata) do
elixir_meta = Map.get(metadata, :elixir_meta, [])
{:ok, {:^, elixir_meta, [var_ex]}}
end
end
# Cons pattern - Used in list patterns [head | tail]
def transform({:cons_pattern, head, tail}, metadata) do
with {:ok, head_ex} <- transform(head, metadata),
{:ok, tail_ex} <- transform(tail, metadata) do
{:ok, [head_ex | tail_ex]}
end
end
# Catch-all for unsupported constructs
def transform(unknown, _metadata) do
{:error, "Unsupported MetaAST construct: #{inspect(unknown)}"}
end
# Helper Functions
defp transform_list(items, metadata) when is_list(items) do
items
|> Enum.reduce_while({:ok, []}, fn item, {:ok, acc} ->
case transform(item, metadata) do
{:ok, ex} -> {:cont, {:ok, [ex | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, items} -> {:ok, Enum.reverse(items)}
error -> error
end
end
defp transform_or_keep_nil(nil, _metadata), do: {:ok, nil}
defp transform_or_keep_nil(value, metadata), do: transform(value, metadata)
defp build_module_alias(parts) do
atoms = Enum.map(parts, &String.to_atom/1)
{:__aliases__, [], atoms}
end
defp transform_match_arms(arms, metadata) do
arms
|> Enum.reduce_while({:ok, []}, fn arm, {:ok, acc} ->
case transform_match_arm(arm, metadata) do
{:ok, arm_ex} -> {:cont, {:ok, [arm_ex | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, arms} -> {:ok, Enum.reverse(arms)}
error -> error
end
end
defp transform_match_arm({:match_arm, pattern, guard, body}, metadata) do
with {:ok, pattern_ex} <- transform_pattern(pattern, metadata),
{:ok, body_ex} <- transform(body, metadata) do
# Ignore guard for now (would need special handling)
_ = guard
{:ok, {:->, [], [[pattern_ex], body_ex]}}
end
end
defp transform_pattern(:_, _metadata) do
# Wildcard pattern
{:ok, {:_, [], nil}}
end
defp transform_pattern(pattern, metadata) do
# Regular pattern is just a transformation
transform(pattern, metadata)
end
defp transform_pattern_to_elixir(:_, _metadata) do
# Wildcard pattern
{:ok, {:_, [], nil}}
end
defp transform_pattern_to_elixir(pattern, metadata) do
# Pattern transformation is the same as regular transformation
# since we handle tuples, pins, and cons patterns in the main transform/2
transform(pattern, metadata)
end
defp transform_params(params, _metadata) do
params_ex =
Enum.map(params, fn
{:param, name, _type_hint, _default} ->
{String.to_atom(name), [], nil}
other ->
# Fallback - shouldn't happen
other
end)
{:ok, params_ex}
end
defp transform_to_comprehension(:map, lambda_ex, collection_ex) do
# Extract variable and body from lambda
case lambda_ex do
{:fn, [], [{:->, [], [[{var, [], nil}], body]}]} ->
# Simple comprehension: for var <- collection, do: body
generator = {:<-, [], [{var, [], nil}, collection_ex]}
{:ok, {:for, [], [generator, [do: body]]}}
_ ->
# Can't convert to comprehension, use Enum.map
enum_module = {:__aliases__, [], [:Enum]}
{:ok, {{:., [], [enum_module, :map]}, [], [collection_ex, lambda_ex]}}
end
end
end