Packages
metastatic
0.8.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/ruby/from_meta.ex
defmodule Metastatic.Adapters.Ruby.FromMeta do
@moduledoc """
Transform MetaAST (M2) back to Ruby AST (M1).
This module implements the reification function ρ_Ruby that converts
meta-level representations back to Ruby-specific AST structures.
## Coverage
M2.1 Core Layer: Fully supported
M2.2 Extended Layer: Partial support (loops, basic iterators)
M2.3 Native Layer: Direct passthrough (preserves original AST)
"""
@doc """
Transform MetaAST back to Ruby AST.
Returns `{:ok, ruby_ast}` on success or `{:error, reason}` on failure.
"""
@spec transform(term(), map()) :: {:ok, term()} | {:error, String.t()}
# M2.1 Core Layer - Literals (New 3-tuple format)
def transform({:literal, meta, value}, _metadata) when is_list(meta) do
subtype = Keyword.get(meta, :subtype)
case subtype do
:integer -> {:ok, %{"type" => "int", "children" => [value]}}
:float -> {:ok, %{"type" => "float", "children" => [value]}}
:string -> {:ok, %{"type" => "str", "children" => [value]}}
:symbol -> {:ok, %{"type" => "sym", "children" => [value]}}
:boolean when value == true -> {:ok, %{"type" => "true", "children" => []}}
:boolean when value == false -> {:ok, %{"type" => "false", "children" => []}}
:null -> {:ok, %{"type" => "nil", "children" => []}}
:constant -> {:ok, %{"type" => "const", "children" => [nil, value]}}
_ -> {:error, "Unknown literal subtype: #{subtype}"}
end
end
# Lists (New 3-tuple format)
def transform({:list, meta, elements}, _metadata) when is_list(meta) do
with {:ok, elements_ast} <- transform_list(elements) do
{:ok, %{"type" => "array", "children" => elements_ast}}
end
end
# Maps (New 3-tuple format)
def transform({:map, meta, pairs}, _metadata) when is_list(meta) do
with {:ok, pairs_ast} <- transform_hash_pairs(pairs) do
{:ok, %{"type" => "hash", "children" => pairs_ast}}
end
end
# Pairs (New 3-tuple format)
def transform({:pair, meta, [key, value]}, _metadata) when is_list(meta) do
with {:ok, key_ast} <- transform(key, %{}),
{:ok, value_ast} <- transform(value, %{}) do
{:ok, %{"type" => "pair", "children" => [key_ast, value_ast]}}
end
end
# M2.1 Core Layer - Variables (New 3-tuple format)
def transform({:variable, meta, name}, _metadata) when is_list(meta) do
scope = Keyword.get(meta, :scope, :local)
case scope do
:local -> {:ok, %{"type" => "lvar", "children" => [name]}}
:instance -> {:ok, %{"type" => "ivar", "children" => [name]}}
:class -> {:ok, %{"type" => "cvar", "children" => [name]}}
:global -> {:ok, %{"type" => "gvar", "children" => [name]}}
:special when name == "self" -> {:ok, %{"type" => "self", "children" => []}}
_ -> {:ok, %{"type" => "lvar", "children" => [name]}}
end
end
# M2.1 Core Layer - Binary Operations (New 3-tuple format)
def transform({:binary_op, meta, [left, right]}, _metadata) when is_list(meta) do
category = Keyword.get(meta, :category)
op = Keyword.get(meta, :operator)
with {:ok, left_ast} <- transform(left, %{}),
{:ok, right_ast} <- transform(right, %{}) do
case category do
:arithmetic -> {:ok, %{"type" => "send", "children" => [left_ast, op, right_ast]}}
:comparison -> {:ok, %{"type" => "send", "children" => [left_ast, op, right_ast]}}
:boolean when op == :and -> {:ok, %{"type" => "and", "children" => [left_ast, right_ast]}}
:boolean when op == :or -> {:ok, %{"type" => "or", "children" => [left_ast, right_ast]}}
end
end
end
# M2.1 Core Layer - Unary Operations (New 3-tuple format)
def transform({:unary_op, meta, [operand]}, _metadata) when is_list(meta) do
category = Keyword.get(meta, :category)
op = Keyword.get(meta, :operator)
with {:ok, operand_ast} <- transform(operand, %{}) do
case {category, op} do
{:arithmetic, _} -> {:ok, %{"type" => "send", "children" => [operand_ast, op, nil]}}
{:boolean, :not} -> {:ok, %{"type" => "send", "children" => [operand_ast, :!, nil]}}
end
end
end
# M2.1 Core Layer - Function Calls (New 3-tuple format)
def transform({:function_call, meta, args}, _metadata) when is_list(meta) do
name = Keyword.get(meta, :name)
with {:ok, args_ast} <- transform_list(args) do
{:ok, %{"type" => "send", "children" => [nil, String.to_atom(name) | args_ast]}}
end
end
# M2.1 Core Layer - Conditionals (New 3-tuple format)
def transform({:conditional, meta, [condition, then_branch, else_branch]}, _metadata)
when is_list(meta) do
with {:ok, cond_ast} <- transform(condition, %{}),
{:ok, then_ast} <- transform_or_nil(then_branch),
{:ok, else_ast} <- transform_or_nil(else_branch) do
{:ok, %{"type" => "if", "children" => [cond_ast, then_ast, else_ast]}}
end
end
# M2.1 Core Layer - Assignment (New 3-tuple format)
def transform({:assignment, meta, [target, value]}, _metadata) when is_list(meta) do
scope = Keyword.get(meta, :scope, :local)
# Extract variable name from target
name =
case target do
{:variable, _, n} -> n
n when is_binary(n) -> n
end
with {:ok, value_ast} <- transform(value, %{}) do
case scope do
:local -> {:ok, %{"type" => "lvasgn", "children" => [name, value_ast]}}
:instance -> {:ok, %{"type" => "ivasgn", "children" => [name, value_ast]}}
:class -> {:ok, %{"type" => "cvasgn", "children" => [name, value_ast]}}
:global -> {:ok, %{"type" => "gvasgn", "children" => [name, value_ast]}}
_ -> {:ok, %{"type" => "lvasgn", "children" => [name, value_ast]}}
end
end
end
# M2.1 Core Layer - Blocks (New 3-tuple format)
def transform({:block, meta, statements}, _metadata) when is_list(meta) do
with {:ok, statements_ast} <- transform_list(statements) do
{:ok, %{"type" => "begin", "children" => statements_ast}}
end
end
# M2.2 Extended Layer - Loops (New 3-tuple format)
def transform({:loop, meta, children}, _metadata) when is_list(meta) do
loop_type = Keyword.get(meta, :loop_type)
case {loop_type, children} do
{:while, [condition, body]} ->
with {:ok, cond_ast} <- transform(condition, %{}),
{:ok, body_ast} <- transform_or_nil(body) do
{:ok, %{"type" => "while", "children" => [cond_ast, body_ast]}}
end
{:for_each, [var, collection, body]} ->
with {:ok, collection_ast} <- transform(collection, %{}),
{:ok, body_ast} <- transform_or_nil(body) do
var_ast = %{"type" => "lvasgn", "children" => [var]}
{:ok, %{"type" => "for", "children" => [var_ast, collection_ast, body_ast]}}
end
end
end
# M2.2s Structural Layer - Container support (New 3-tuple format)
def transform({:container, meta, [body]}, _metadata) when is_list(meta) do
container_type = Keyword.get(meta, :container_type)
name = Keyword.get(meta, :name)
parent = Keyword.get(meta, :parent)
with {:ok, name_ast} <- build_const_ast(name),
{:ok, body_ast} <- transform_or_nil(body) do
case container_type do
:class ->
{:ok, parent_ast} = build_const_ast_or_nil(parent)
{:ok, %{"type" => "class", "children" => [name_ast, parent_ast, body_ast]}}
:module ->
{:ok, %{"type" => "module", "children" => [name_ast, body_ast]}}
end
end
end
# Handle container with location (4-tuple)
def transform({:container, meta, [body], _loc}, metadata) when is_list(meta) do
transform({:container, meta, [body]}, metadata)
end
# M2.2s Structural Layer - Function definition support (New 3-tuple format)
def transform({:function_def, meta, [body]}, _metadata) when is_list(meta) do
name = Keyword.get(meta, :name)
params = Keyword.get(meta, :params, [])
args_ast = build_args_ast(params)
with {:ok, body_ast} <- transform_or_nil(body) do
# Check if it's a class method (self.method_name)
if String.starts_with?(name, "self.") do
method_name = String.replace_prefix(name, "self.", "")
self_ast = %{"type" => "self", "children" => []}
{:ok, %{"type" => "defs", "children" => [self_ast, method_name, args_ast, body_ast]}}
else
{:ok, %{"type" => "def", "children" => [name, args_ast, body_ast]}}
end
end
end
# Handle function_def with location (4-tuple)
def transform({:function_def, meta, [body], _loc}, metadata) when is_list(meta) do
transform({:function_def, meta, [body]}, metadata)
end
# M2.2s Structural Layer - Attribute access (New 3-tuple format)
def transform({:attribute_access, meta, [receiver]}, _metadata) when is_list(meta) do
attribute = Keyword.get(meta, :attribute)
with {:ok, receiver_ast} <- transform(receiver, %{}) do
{:ok, %{"type" => "send", "children" => [receiver_ast, String.to_atom(attribute)]}}
end
end
# M2.2s Structural Layer - Augmented assignment (New 3-tuple format)
def transform({:augmented_assignment, meta, [target, value]}, _metadata) when is_list(meta) do
op = Keyword.get(meta, :operator)
with {:ok, target_ast} <- transform(target, %{}),
{:ok, value_ast} <- transform(value, %{}) do
{:ok, %{"type" => "op_asgn", "children" => [target_ast, op, value_ast]}}
end
end
# M2.3 Native Layer - Passthrough (New 3-tuple format)
def transform({:language_specific, meta, original_ast}, _metadata) when is_list(meta) do
{:ok, original_ast}
end
# Nil passthrough
def transform(nil, _metadata), do: {:ok, nil}
# Catch-all
def transform(unsupported, _metadata) do
{:error, "Unsupported MetaAST construct for Ruby reification: #{inspect(unsupported)}"}
end
# Helper Functions
defp transform_list(items) when is_list(items) do
items
|> Enum.reduce_while({:ok, []}, fn item, {:ok, acc} ->
case transform(item, %{}) do
{:ok, ast} -> {:cont, {:ok, [ast | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, items_ast} -> {:ok, Enum.reverse(items_ast)}
error -> error
end
end
defp transform_or_nil(nil), do: {:ok, nil}
defp transform_or_nil(value), do: transform(value, %{})
defp transform_hash_pairs(pairs) when is_list(pairs) do
pairs
|> Enum.reduce_while({:ok, []}, fn pair, {:ok, acc} ->
case transform(pair, %{}) do
{:ok, ast} -> {:cont, {:ok, [ast | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, pairs_ast} -> {:ok, Enum.reverse(pairs_ast)}
error -> error
end
end
# Helper to build constant AST node
defp build_const_ast(name) when is_binary(name) do
{:ok, %{"type" => "const", "children" => [nil, name]}}
end
defp build_const_ast_or_nil(nil), do: {:ok, nil}
defp build_const_ast_or_nil(name), do: build_const_ast(name)
# Helper to build args AST for method definitions
defp build_args_ast(params) when is_list(params) do
children =
Enum.map(params, fn
{:param, meta, name} when is_binary(name) ->
# New 3-tuple format: {:param, [pattern: pattern, default: default], name}
pattern = Keyword.get(meta, :pattern)
default = Keyword.get(meta, :default)
cond do
pattern != nil ->
%{"type" => "arg", "children" => ["_pattern"]}
default != nil ->
%{"type" => "optarg", "children" => [name]}
true ->
%{"type" => "arg", "children" => [name]}
end
param when is_binary(param) ->
%{"type" => "arg", "children" => [param]}
{:pattern, _meta} ->
# Legacy pattern parameters
%{"type" => "arg", "children" => ["_pattern"]}
{:default, name, _default_val} ->
# Legacy default parameters
%{"type" => "arg", "children" => [name]}
end)
%{"type" => "args", "children" => children}
end
end