Packages
metastatic
0.6.0
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/to_meta.ex
defmodule Metastatic.Adapters.Ruby.ToMeta do
@moduledoc """
Transform Ruby AST (M1) to MetaAST (M2).
This module implements the abstraction function α_Ruby that lifts
Ruby-specific AST structures to the meta-level representation.
## Transformation Strategy
The transformation follows a pattern-matching approach, handling each
Ruby AST construct (from parser gem) and mapping it to the appropriate MetaAST node type.
### M2.1 (Core Layer)
- Literals: integers, floats, strings, booleans, nil, symbols
- Variables: local, instance, class, global
- Binary operators: arithmetic, comparison, boolean
- Unary operators: negation, logical not
- Method calls
- Conditionals: if/elsif/else, unless, ternary
- Blocks: sequential expressions
- Assignment: local variable assignment
### M2.2 (Extended Layer)
- Loops: while, until, for
- Iterators: each, map, select, reduce
- Blocks & Procs: blocks, lambdas, procs
- Pattern matching: case/when (classic), case/in (Ruby 3+)
- Exception handling: begin/rescue/ensure
### M2.3 (Native Layer)
- Class definitions
- Module definitions
- Method definitions
- String interpolation
- Regular expressions
- Metaprogramming constructs
"""
@doc """
Transform Ruby AST to MetaAST.
Returns `{:ok, meta_ast, metadata}` on success or `{:error, reason}` on failure.
## Examples
iex> transform(%{"type" => "int", "children" => [42]})
{:ok, {:literal, :integer, 42}, %{}}
iex> transform(%{"type" => "lvar", "children" => ["x"]})
{:ok, {:variable, "x"}, %{}}
"""
@spec transform(term()) :: {:ok, term(), map()} | {:error, String.t()}
# Literals - M2.1 Core Layer
# Integer literal
def transform(%{"type" => "int", "children" => [value]} = ast) when is_integer(value) do
{:ok, add_location({:literal, :integer, value}, ast), %{}}
end
# Float literal
def transform(%{"type" => "float", "children" => [value]} = ast) when is_float(value) do
{:ok, add_location({:literal, :float, value}, ast), %{}}
end
# String literal
def transform(%{"type" => "str", "children" => [value]} = ast) when is_binary(value) do
{:ok, add_location({:literal, :string, value}, ast), %{}}
end
# Symbol literal
def transform(%{"type" => "sym", "children" => [value]})
when is_atom(value) or is_binary(value) do
symbol = if is_binary(value), do: String.to_atom(value), else: value
{:ok, {:literal, :symbol, symbol}, %{}}
end
# Boolean literals
def transform(%{"type" => "true", "children" => []}) do
{:ok, {:literal, :boolean, true}, %{}}
end
def transform(%{"type" => "false", "children" => []}) do
{:ok, {:literal, :boolean, false}, %{}}
end
# Nil literal
def transform(%{"type" => "nil", "children" => []}) do
{:ok, {:literal, :null, nil}, %{}}
end
# Self keyword
def transform(%{"type" => "self", "children" => []}) do
{:ok, {:variable, "self"}, %{scope: :special}}
end
# Handle bare nil (used in unary operators)
def transform(nil) do
{:ok, nil, %{}}
end
# Array literal
def transform(%{"type" => "array", "children" => elements}) do
with {:ok, elements_meta} <- transform_list(elements) do
{:ok, {:literal, :collection, elements_meta}, %{collection_type: :array}}
end
end
# Constant (e.g., StandardError, Array, Hash)
def transform(%{"type" => "const", "children" => [namespace, name]}) do
const_name = if is_atom(name), do: Atom.to_string(name), else: name
qualified_name =
if is_nil(namespace) do
const_name
else
with {:ok, namespace_meta, _} <- transform(namespace) do
"#{format_const(namespace_meta)}::#{const_name}"
end
end
case qualified_name do
{:ok, _, _} = result -> result
name when is_binary(name) -> {:ok, {:literal, :constant, name}, %{}}
end
end
# Hash literal
def transform(%{"type" => "hash", "children" => pairs}) do
with {:ok, pairs_meta} <- transform_hash_pairs(pairs) do
{:ok, {:literal, :collection, pairs_meta}, %{collection_type: :hash}}
end
end
# Variables - M2.1 Core Layer
# Local variable
def transform(%{"type" => "lvar", "children" => [name]} = ast)
when is_binary(name) or is_atom(name) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, add_location({:variable, var_name}, ast), %{scope: :local}}
end
# Instance variable (@var)
def transform(%{"type" => "ivar", "children" => [name]})
when is_binary(name) or is_atom(name) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, {:variable, var_name}, %{scope: :instance}}
end
# Class variable (@@var)
def transform(%{"type" => "cvar", "children" => [name]})
when is_binary(name) or is_atom(name) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, {:variable, var_name}, %{scope: :class}}
end
# Global variable ($var)
def transform(%{"type" => "gvar", "children" => [name]})
when is_binary(name) or is_atom(name) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, {:variable, var_name}, %{scope: :global}}
end
# Binary Operations - M2.1 Core Layer
# In Ruby, most binary operators are implemented as method calls (send nodes)
# Arithmetic operators: +, -, *, /, %, **
# Operators can be either atoms or strings from JSON
def transform(%{"type" => "send", "children" => [left, op, right]} = ast)
when not is_nil(right) do
cond do
is_arithmetic_op?(op) ->
op_atom = normalize_op(op)
with {:ok, left_meta, _} <- transform(left),
{:ok, right_meta, _} <- transform(right) do
{:ok, add_location({:binary_op, :arithmetic, op_atom, left_meta, right_meta}, ast), %{}}
end
is_comparison_op?(op) ->
op_atom = normalize_op(op)
with {:ok, left_meta, _} <- transform(left),
{:ok, right_meta, _} <- transform(right) do
{:ok, add_location({:binary_op, :comparison, op_atom, left_meta, right_meta}, ast), %{}}
end
true ->
# Regular method call with receiver
transform_method_call_with_receiver(left, op, right)
end
end
# Boolean operators: &&, ||, and, or
def transform(%{"type" => "and", "children" => [left, right]}) do
with {:ok, left_meta, _} <- transform(left),
{:ok, right_meta, _} <- transform(right) do
{:ok, {:binary_op, :boolean, :and, left_meta, right_meta}, %{}}
end
end
def transform(%{"type" => "or", "children" => [left, right]}) do
with {:ok, left_meta, _} <- transform(left),
{:ok, right_meta, _} <- transform(right) do
{:ok, {:binary_op, :boolean, :or, left_meta, right_meta}, %{}}
end
end
# Unary Operations - M2.1 Core Layer
# Unary operators - 3 children with nil as third: [operand, op, nil]
def transform(%{"type" => "send", "children" => [operand, op, nil]}) do
op_atom = normalize_op(op)
cond do
op_atom in [:-, :+] ->
with {:ok, operand_meta, _} <- transform(operand) do
{:ok, {:unary_op, :arithmetic, op_atom, operand_meta}, %{}}
end
op_atom == :! ->
with {:ok, operand_meta, _} <- transform(operand) do
{:ok, {:unary_op, :boolean, :not, operand_meta}, %{}}
end
true ->
# Not actually a unary operator
{:error, "Invalid unary operator: #{op}"}
end
end
# Method call without arguments: 2 children [receiver, method]
def transform(%{"type" => "send", "children" => [receiver, method]}) do
method_str = if is_atom(method), do: Atom.to_string(method), else: method
if is_nil(receiver) do
# Local method call without args: hello
{:ok, {:function_call, method_str, []}, %{call_type: :local}}
else
# Method call with receiver but no arguments: obj.method
with {:ok, receiver_meta, _} <- transform(receiver) do
qualified_name = "#{format_receiver(receiver_meta)}.#{method_str}"
{:ok, {:function_call, qualified_name, []}, %{call_type: :instance}}
end
end
end
# Method Calls - M2.1 Core Layer
# Method call without receiver (local method call)
def transform(%{"type" => "send", "children" => [nil, method_name | args]}) do
method_str = if is_atom(method_name), do: Atom.to_string(method_name), else: method_name
with {:ok, args_meta} <- transform_list(args) do
{:ok, {:function_call, method_str, args_meta}, %{call_type: :local}}
end
end
# Method call with receiver
def transform(%{"type" => "send", "children" => [receiver, method_name | args]})
when not is_nil(receiver) and is_atom(method_name) do
with {:ok, receiver_meta, _} <- transform(receiver),
{:ok, args_meta} <- transform_list(args) do
# For now, represent as "receiver.method" format
# In future, might want to preserve receiver as separate field
method_str = "#{format_receiver(receiver_meta)}.#{method_name}"
{:ok, {:function_call, method_str, args_meta}, %{call_type: :instance}}
end
end
# Conditionals - M2.1 Core Layer
# if/elsif/else
def transform(%{"type" => "if", "children" => [condition, then_branch, else_branch]}) do
with {:ok, cond_meta, _} <- transform(condition),
{:ok, then_meta, _} <- transform_or_nil(then_branch),
{:ok, else_meta, _} <- transform_or_nil(else_branch) do
{:ok, {:conditional, cond_meta, then_meta, else_meta}, %{}}
end
end
# Ternary operator (also parsed as if)
# Note: Ruby's parser treats ternary the same as if
# Assignment - M2.1 Core Layer
# Local variable assignment
def transform(%{"type" => "lvasgn", "children" => [name, value]}) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
with {:ok, value_meta, _} <- transform(value) do
{:ok, {:assignment, {:variable, var_name}, value_meta}, %{scope: :local}}
end
end
# Local variable binding (without value, e.g., in rescue clauses)
def transform(%{"type" => "lvasgn", "children" => [name]}) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, {:variable, var_name}, %{scope: :local, binding: true}}
end
# Instance variable assignment
def transform(%{"type" => "ivasgn", "children" => [name, value]}) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
with {:ok, value_meta, _} <- transform(value) do
{:ok, {:assignment, {:variable, var_name}, value_meta}, %{scope: :instance}}
end
end
# Blocks - M2.1 Core Layer
# Begin block (sequential statements)
def transform(%{"type" => "begin", "children" => statements}) when is_list(statements) do
with {:ok, statements_meta} <- transform_list(statements) do
{:ok, {:block, statements_meta}, %{}}
end
end
# Loops - M2.2 Extended Layer
# While loop: while condition do body end
def transform(%{"type" => "while", "children" => [condition, body]}) do
with {:ok, cond_meta, _} <- transform(condition),
{:ok, body_meta, _} <- transform_or_nil(body) do
{:ok, {:loop, :while, cond_meta, body_meta}, %{}}
end
end
# Until loop: until condition do body end
def transform(%{"type" => "until", "children" => [condition, body]}) do
with {:ok, cond_meta, _} <- transform(condition),
{:ok, body_meta, _} <- transform_or_nil(body) do
# Until is equivalent to "while not condition"
negated_cond = {:unary_op, :boolean, :not, cond_meta}
{:ok, {:loop, :while, negated_cond, body_meta}, %{original_type: :until}}
end
end
# For loop: for var in collection do body end
def transform(%{"type" => "for", "children" => [var_asgn, collection, body]}) do
with {:ok, var_meta, _} <- transform_iterator_variable(var_asgn),
{:ok, collection_meta, _} <- transform(collection),
{:ok, body_meta, _} <- transform_or_nil(body) do
{:ok, {:loop, :for_each, var_meta, collection_meta, body_meta}, %{}}
end
end
# Blocks with parameters and iterators - M2.2 Extended Layer
# Block with receiver (iterator methods: each, map, select, reduce, etc.)
# Structure: block [send [collection, method, ...args], args [param...], body]
def transform(%{"type" => "block", "children" => [send_node, args_node, body]}) do
case send_node do
# Lambda: lambda { |x| body } or ->(x) { body }
%{"type" => "send", "children" => [nil, "lambda"]} ->
transform_lambda(args_node, body)
# Iterator methods: [1,2,3].each { |x| ... }
%{"type" => "send", "children" => [collection, method | method_args]} ->
transform_iterator_method(collection, method, method_args, args_node, body)
_ ->
{:error, "Unsupported block construct: #{inspect(send_node)}"}
end
end
# Pattern Matching - M2.2 Extended Layer
# Case/when statement
def transform(%{"type" => "case", "children" => children}) do
[scrutinee | branches] = children
{else_branch, when_branches} = extract_else_branch(branches)
with {:ok, scrutinee_meta, _} <- transform(scrutinee),
{:ok, branches_meta} <- transform_when_branches(when_branches),
{:ok, else_meta, _} <- transform_or_nil(else_branch) do
{:ok, {:pattern_match, scrutinee_meta, branches_meta, else_meta}, %{}}
end
end
# Exception Handling - M2.2 Extended Layer
# Begin/rescue/ensure block
def transform(%{"type" => "kwbegin", "children" => [ensure_or_rescue]}) do
transform(ensure_or_rescue)
end
# Ensure block (with or without rescue)
def transform(%{"type" => "ensure", "children" => [try_body, ensure_body]}) do
case try_body do
%{"type" => "rescue"} ->
# Has both rescue and ensure
with {:ok, rescue_meta, rescue_metadata} <- transform(try_body),
{:ok, ensure_meta, _} <- transform(ensure_body) do
# Merge rescue handlers with ensure
metadata = Map.put(rescue_metadata, :ensure, ensure_meta)
{:ok, rescue_meta, metadata}
end
_ ->
# Only ensure, no rescue
with {:ok, try_meta, _} <- transform(try_body),
{:ok, ensure_meta, _} <- transform(ensure_body) do
{:ok, {:exception_handling, try_meta, [], nil}, %{ensure: ensure_meta}}
end
end
end
# Rescue block
def transform(%{"type" => "rescue", "children" => children}) do
[try_body | rescue_bodies] = children
rescue_handlers = Enum.filter(rescue_bodies, &match?(%{"type" => "resbody"}, &1))
else_body = Enum.find(rescue_bodies, fn node -> not match?(%{"type" => "resbody"}, node) end)
with {:ok, try_meta, _} <- transform(try_body),
{:ok, handlers_meta} <- transform_rescue_handlers(rescue_handlers),
{:ok, else_meta, _} <- transform_or_nil(else_body) do
{:ok, {:exception_handling, try_meta, handlers_meta, else_meta}, %{}}
end
end
# M2.3 Native Layer - Ruby-specific constructs
# Class definition
def transform(%{"type" => "class", "children" => [name, superclass, body]} = ast) do
with {:ok, name_meta, _} <- transform(name),
{:ok, superclass_meta, _} <- transform_or_nil(superclass),
{:ok, body_meta, _} <- transform_or_nil(body) do
metadata = %{
name: extract_constant_name(name_meta),
superclass: superclass_meta,
body: body_meta
}
{:ok, {:language_specific, :ruby, ast, :class_definition}, metadata}
end
end
# Module definition
def transform(%{"type" => "module", "children" => [name, body]} = ast) do
with {:ok, name_meta, _} <- transform(name),
{:ok, body_meta, _} <- transform_or_nil(body) do
metadata = %{
name: extract_constant_name(name_meta),
body: body_meta
}
{:ok, {:language_specific, :ruby, ast, :module_definition}, metadata}
end
end
# Method definition (def)
def transform(%{"type" => "def", "children" => [name, args, body]} = ast) do
method_name = if is_atom(name), do: Atom.to_string(name), else: name
with {:ok, params} <- extract_method_params(args),
{:ok, body_meta, _} <- transform_or_nil(body) do
metadata = %{
name: method_name,
params: params,
body: body_meta
}
{:ok, {:language_specific, :ruby, ast, :method_definition}, metadata}
end
end
# Constant assignment (e.g., BAR = 42)
def transform(%{"type" => "casgn", "children" => [namespace, name, value]} = ast) do
const_name = if is_atom(name), do: Atom.to_string(name), else: name
with {:ok, value_meta, _} <- transform(value) do
metadata = %{
name: const_name,
namespace: namespace,
value: value_meta
}
{:ok, {:language_specific, :ruby, ast, :constant_assignment}, metadata}
end
end
# Yield with arguments
def transform(%{"type" => "yield", "children" => args} = ast) do
with {:ok, args_meta} <- transform_list(args) do
metadata = %{args: args_meta}
{:ok, {:language_specific, :ruby, ast, :yield}, metadata}
end
end
# Alias (alias new_name old_name)
def transform(%{"type" => "alias", "children" => [new_name, old_name]} = ast) do
new_sym = extract_symbol_name(new_name)
old_sym = extract_symbol_name(old_name)
metadata = %{new_name: new_sym, old_name: old_sym}
{:ok, {:language_specific, :ruby, ast, :alias}, metadata}
end
# String interpolation (dstr)
def transform(%{"type" => "dstr", "children" => parts} = ast) do
with {:ok, parts_meta} <- transform_string_parts(parts) do
metadata = %{parts: parts_meta}
{:ok, {:language_specific, :ruby, ast, :string_interpolation}, metadata}
end
end
# Regular expression
def transform(%{"type" => "regexp", "children" => [pattern | options]} = ast) do
with {:ok, pattern_meta, _} <- transform(pattern) do
metadata = %{pattern: pattern_meta, options: options}
{:ok, {:language_specific, :ruby, ast, :regexp}, metadata}
end
end
# Singleton class (class << self)
def transform(%{"type" => "sclass", "children" => [object, body]} = ast) do
with {:ok, object_meta, _} <- transform(object),
{:ok, body_meta, _} <- transform_or_nil(body) do
metadata = %{object: object_meta, body: body_meta}
{:ok, {:language_specific, :ruby, ast, :singleton_class}, metadata}
end
end
# Super with arguments
def transform(%{"type" => "super", "children" => args} = ast) do
with {:ok, args_meta} <- transform_list(args) do
metadata = %{args: args_meta}
{:ok, {:language_specific, :ruby, ast, :super}, metadata}
end
end
# Zsuper (super with no arguments, passes all parent arguments)
def transform(%{"type" => "zsuper", "children" => []} = ast) do
{:ok, {:language_specific, :ruby, ast, :zsuper}, %{}}
end
# Catch-all for unsupported constructs
def transform(unsupported) do
{:error, "Unsupported Ruby AST construct: #{inspect(unsupported)}"}
end
# Helper Functions
defp transform_list(items) when is_list(items) do
items
|> Enum.reject(&is_nil/1)
|> Enum.reduce_while({:ok, []}, fn item, {:ok, acc} ->
case transform(item) do
{:ok, meta, _} -> {:cont, {:ok, [meta | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, items} -> {:ok, Enum.reverse(items)}
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_hash_pair(pair) do
{:ok, pair_meta} -> {:cont, {:ok, [pair_meta | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, pairs} -> {:ok, Enum.reverse(pairs)}
error -> error
end
end
defp transform_hash_pair(%{"type" => "pair", "children" => [key, value]}) do
with {:ok, key_meta, _} <- transform(key),
{:ok, value_meta, _} <- transform(value) do
{:ok, {:tuple, [key_meta, value_meta]}}
end
end
defp transform_hash_pair(other) do
{:error, "Invalid hash pair: #{inspect(other)}"}
end
defp format_receiver({:variable, name}), do: name
defp format_receiver(_), do: "obj"
defp format_const({:literal, :constant, name}), do: name
defp format_const(_), do: "Unknown"
# Operator normalization helpers
defp normalize_op(op) when is_atom(op), do: op
defp normalize_op(op) when is_binary(op), do: String.to_atom(op)
defp is_arithmetic_op?(op) when is_atom(op) do
op in [:+, :-, :*, :/, :%, :**] or op == :"<<" or op == :">>"
end
defp is_arithmetic_op?(op) when is_binary(op) do
op in ["+", "-", "*", "/", "%", "**", "<<", ">>"]
end
defp is_comparison_op?(op) when is_atom(op) do
op in [:==, :!=, :<, :>, :<=, :>=, :===, :eql?, :"<=>"]
end
defp is_comparison_op?(op) when is_binary(op) do
op in ["==", "!=", "<", ">", "<=", ">=", "===", "eql?", "<=>"]
end
defp transform_method_call_with_receiver(receiver, method_name, arg) do
method_str = if is_atom(method_name), do: Atom.to_string(method_name), else: method_name
with {:ok, receiver_meta, _} <- transform(receiver),
{:ok, arg_meta, _} <- transform(arg) do
qualified_name = "#{format_receiver(receiver_meta)}.#{method_str}"
{:ok, {:function_call, qualified_name, [arg_meta]}, %{call_type: :instance}}
end
end
# M2.2 Extended Layer Helper Functions
# Transform iterator variable (from lvasgn in for loops)
defp transform_iterator_variable(%{"type" => "lvasgn", "children" => [name]}) do
var_name = if is_atom(name), do: Atom.to_string(name), else: name
{:ok, var_name, %{}}
end
defp transform_iterator_variable(other) do
{:error, "Invalid iterator variable: #{inspect(other)}"}
end
# Transform lambda
defp transform_lambda(args_node, body) do
with {:ok, params} <- extract_lambda_params(args_node),
{:ok, body_meta, _} <- transform(body) do
{:ok, {:lambda, params, body_meta}, %{}}
end
end
# Extract lambda parameters from args node
defp extract_lambda_params(%{"type" => "args", "children" => args}) do
params =
Enum.map(args, fn
%{"type" => "arg", "children" => [name]} when is_binary(name) -> name
%{"type" => "arg", "children" => [name]} when is_atom(name) -> Atom.to_string(name)
_ -> nil
end)
|> Enum.reject(&is_nil/1)
{:ok, params}
end
defp extract_lambda_params(_), do: {:ok, []}
# Transform iterator methods (map, each, select, reduce)
defp transform_iterator_method(collection, method, method_args, args_node, body) do
method_atom = normalize_op(method)
case method_atom do
m when m in [:each, :map, :select, :filter, :reject] ->
transform_map_like_iterator(collection, m, args_node, body)
:reduce ->
transform_reduce_iterator(collection, method_args, args_node, body)
_ ->
# Not a recognized iterator, treat as generic block
{:error, "Unrecognized iterator method: #{method}"}
end
end
# Transform map-like iterators (each, map, select, filter)
defp transform_map_like_iterator(collection, method, args_node, body) do
with {:ok, collection_meta, _} <- transform(collection),
{:ok, params} <- extract_lambda_params(args_node),
{:ok, body_meta, _} <- transform(body) do
lambda = {:lambda, params, body_meta}
{:ok, {:collection_op, method, lambda, collection_meta}, %{}}
end
end
# Transform reduce iterator
defp transform_reduce_iterator(collection, method_args, args_node, body) do
with {:ok, collection_meta, _} <- transform(collection),
{:ok, initial_meta} <- extract_reduce_initial(method_args),
{:ok, params} <- extract_lambda_params(args_node),
{:ok, body_meta, _} <- transform(body) do
lambda = {:lambda, params, body_meta}
{:ok, {:collection_op, :reduce, lambda, collection_meta, initial_meta}, %{}}
end
end
# Extract initial value from reduce method args
defp extract_reduce_initial([initial | _]) do
case transform(initial) do
{:ok, meta, _} -> {:ok, meta}
error -> error
end
end
defp extract_reduce_initial([]), do: {:ok, nil}
# Pattern matching helpers
defp extract_else_branch(branches) do
else_branch = Enum.find(branches, fn node -> not match?(%{"type" => "when"}, node) end)
when_branches = Enum.filter(branches, &match?(%{"type" => "when"}, &1))
{else_branch, when_branches}
end
defp transform_when_branches(when_branches) do
when_branches
|> Enum.reduce_while({:ok, []}, fn when_node, {:ok, acc} ->
case transform_when_branch(when_node) do
{:ok, branch_meta} -> {:cont, {:ok, [branch_meta | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, branches} -> {:ok, Enum.reverse(branches)}
error -> error
end
end
defp transform_when_branch(%{"type" => "when", "children" => [pattern | rest]}) do
# Rest is either [body] or [] if body is nil
body = List.first(rest)
with {:ok, pattern_meta, _} <- transform(pattern),
{:ok, body_meta, _} <- transform_or_nil(body) do
{:ok, {pattern_meta, body_meta}}
end
end
# Exception handling helpers
defp transform_rescue_handlers(handlers) do
handlers
|> Enum.reduce_while({:ok, []}, fn handler, {:ok, acc} ->
case transform_rescue_handler(handler) do
{:ok, handler_meta} -> {:cont, {:ok, [handler_meta | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, handlers} -> {:ok, Enum.reverse(handlers)}
error -> error
end
end
defp transform_rescue_handler(%{
"type" => "resbody",
"children" => [exception_types, var_binding, body]
}) do
with {:ok, types_meta} <- transform_exception_types(exception_types),
{:ok, var_meta, _} <- transform_or_nil(var_binding),
{:ok, body_meta, _} <- transform(body) do
{:ok, {types_meta, var_meta, body_meta}}
end
end
defp transform_exception_types(%{"type" => "array", "children" => types}) do
transform_list(types)
end
defp transform_exception_types(nil), do: {:ok, []}
# M2.3 Native Layer Helpers
defp extract_constant_name({:literal, :constant, name}), do: name
defp extract_constant_name(_), do: "Unknown"
defp extract_method_params(%{"type" => "args", "children" => args}) do
params =
Enum.map(args, fn
%{"type" => "arg", "children" => [name]} when is_binary(name) -> name
%{"type" => "arg", "children" => [name]} when is_atom(name) -> Atom.to_string(name)
_ -> nil
end)
|> Enum.reject(&is_nil/1)
{:ok, params}
end
defp extract_method_params(_), do: {:ok, []}
defp extract_symbol_name(%{"type" => "sym", "children" => [name]}) when is_atom(name) do
Atom.to_string(name)
end
defp extract_symbol_name(%{"type" => "sym", "children" => [name]}) when is_binary(name),
do: name
defp extract_symbol_name(%{"type" => "dsym", "children" => parts}), do: {:dynamic, parts}
defp extract_symbol_name(_), do: "unknown"
defp transform_string_parts(parts) when is_list(parts) do
parts
|> Enum.reduce_while({:ok, []}, fn part, {:ok, acc} ->
case part do
# String literal part
%{"type" => "str", "children" => [str]} ->
{:cont, {:ok, [{:literal, str} | acc]}}
# Interpolated expression
%{"type" => "begin", "children" => [expr]} ->
case transform(expr) do
{:ok, expr_meta, _} -> {:cont, {:ok, [{:interpolation, expr_meta} | acc]}}
{:error, _} = err -> {:halt, err}
end
# Direct expression
expr ->
case transform(expr) do
{:ok, expr_meta, _} -> {:cont, {:ok, [{:interpolation, expr_meta} | acc]}}
{:error, _} = err -> {:halt, err}
end
end
end)
|> case do
{:ok, parts_list} -> {:ok, Enum.reverse(parts_list)}
error -> error
end
end
# Location extraction helper
@doc false
@spec add_location(term(), map()) :: map() | nil
defp add_location(meta_ast, %{"location" => loc}) when is_map(loc) do
location =
%{}
|> maybe_put(:line, loc["begin_line"])
|> maybe_put(:col, loc["begin_column"])
|> maybe_put(:end_line, loc["begin_line"])
|> maybe_put(:end_col, nil)
if map_size(location) > 0 do
Tuple.insert_at(meta_ast, tuple_size(meta_ast), location)
else
meta_ast
end
end
defp add_location(meta_ast, _ast_node), do: meta_ast
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
end