Packages
metastatic
0.7.7
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/cli/inspector.ex
defmodule Metastatic.CLI.Inspector do
@moduledoc """
AST inspection and analysis logic for Metastatic CLI.
Provides various inspection capabilities:
- Layer filtering (core, extended, native)
- Variable extraction
- Node counting
- Depth analysis
"""
alias Metastatic.{AST, Document, Validator}
@type layer :: :core | :extended | :native | :all
@type inspection_result :: %{
ast: AST.meta_ast(),
variables: MapSet.t(String.t()),
layer: layer(),
depth: non_neg_integer(),
node_count: non_neg_integer()
}
@doc """
Inspect a MetaAST document.
Returns detailed information about the AST structure.
"""
@spec inspect_document(Document.t(), keyword()) ::
{:ok, inspection_result()} | {:error, String.t()}
def inspect_document(%Document{} = doc, opts \\ []) do
layer_filter = Keyword.get(opts, :layer, :all)
ast =
if layer_filter == :all do
doc.ast
else
filter_by_layer(doc.ast, layer_filter)
end
variables = AST.variables(doc.ast)
depth = calculate_depth(doc.ast)
node_count = count_nodes(doc.ast)
layer = detect_layer(doc.ast)
result = %{
ast: ast,
variables: variables,
layer: layer,
depth: depth,
node_count: node_count
}
{:ok, result}
end
@doc """
Extract only variables from a MetaAST.
"""
@spec extract_variables(Document.t()) :: {:ok, MapSet.t(String.t())}
def extract_variables(%Document{} = doc) do
{:ok, AST.variables(doc.ast)}
end
@doc """
Validate a document and return detailed validation metadata.
"""
@spec validate_with_details(Document.t(), atom()) ::
{:ok, Validator.validation_result()} | {:error, term()}
def validate_with_details(%Document{} = doc, mode \\ :standard) do
Validator.validate(doc, mode: mode)
end
# Private functions
@spec filter_by_layer(AST.meta_ast(), layer()) :: AST.meta_ast() | nil
defp filter_by_layer(ast, layer) do
node_layer = node_layer(ast)
cond do
# If the node matches the requested layer, keep it
node_layer == layer ->
ast
# If it's a composite node, filter its children
composite_node?(ast) ->
filter_composite_children(ast, layer)
# Otherwise, exclude this node
true ->
nil
end
end
@spec filter_composite_children(AST.meta_ast(), layer()) :: AST.meta_ast() | nil
defp filter_composite_children({:binary_op, category, op, left, right}, layer) do
filtered_left = filter_by_layer(left, layer)
filtered_right = filter_by_layer(right, layer)
if filtered_left || filtered_right do
{:binary_op, category, op, filtered_left || left, filtered_right || right}
else
nil
end
end
defp filter_composite_children({:block, statements}, layer) do
filtered_statements =
statements
|> Enum.map(&filter_by_layer(&1, layer))
|> Enum.reject(&is_nil/1)
if Enum.empty?(filtered_statements) do
nil
else
{:block, filtered_statements}
end
end
defp filter_composite_children(ast, _layer), do: ast
@node_layers %{
core:
~w|literal variable list map binary_op unary_op function_call conditional block early_return|a,
extended: ~w|loop lambda collection_op exception_handling|a,
native: ~w|language_specific|a
}
@spec node_layer(AST.meta_ast()) :: layer()
# Handle location-aware nodes
for {layer, kinds} <- @node_layers do
defp node_layer(ast) when is_tuple(ast) and elem(ast, 0) in unquote(kinds), do: unquote(layer)
end
defp node_layer(_), do: :core
@composite_nodes ~w|binary_op unary_op list map function_call conditional block loop lambda collection_op exception_handling|a
@spec composite_node?(AST.meta_ast()) :: boolean()
# Handle location-aware nodes
defp composite_node?(ast) when is_tuple(ast) and elem(ast, 0) in @composite_nodes, do: true
defp composite_node?(_), do: false
@spec calculate_depth(AST.meta_ast()) :: non_neg_integer()
# Handle location-aware nodes
defp calculate_depth({:literal, _, _, _loc}), do: 1
defp calculate_depth({:literal, _, _}), do: 1
defp calculate_depth({:variable, _, _loc}), do: 1
defp calculate_depth({:variable, _}), do: 1
defp calculate_depth({:binary_op, _, _, left, right, _loc}) do
1 + max(calculate_depth(left), calculate_depth(right))
end
defp calculate_depth({:binary_op, _, _, left, right}) do
1 + max(calculate_depth(left), calculate_depth(right))
end
defp calculate_depth({:unary_op, _, _, operand, _loc}) do
1 + calculate_depth(operand)
end
defp calculate_depth({:unary_op, _, _, operand}) do
1 + calculate_depth(operand)
end
defp calculate_depth({:function_call, _, args, _loc}) do
if Enum.empty?(args) do
1
else
1 + (Enum.map(args, &calculate_depth/1) |> Enum.max())
end
end
defp calculate_depth({:function_call, _, args}) do
if Enum.empty?(args) do
1
else
1 + (Enum.map(args, &calculate_depth/1) |> Enum.max())
end
end
defp calculate_depth({:conditional, condition, then_branch, else_branch}) do
depths = [calculate_depth(condition), calculate_depth(then_branch)]
depths =
if else_branch do
depths ++ [calculate_depth(else_branch)]
else
depths
end
1 + Enum.max(depths)
end
defp calculate_depth({:block, statements}) do
if Enum.empty?(statements) do
1
else
1 + (Enum.map(statements, &calculate_depth/1) |> Enum.max())
end
end
defp calculate_depth({:list, elements}) do
if Enum.empty?(elements) do
1
else
1 + (Enum.map(elements, &calculate_depth/1) |> Enum.max())
end
end
defp calculate_depth({:map, pairs}) do
if Enum.empty?(pairs) do
1
else
1 +
(Enum.flat_map(pairs, fn {k, v} -> [calculate_depth(k), calculate_depth(v)] end)
|> Enum.max())
end
end
defp calculate_depth({:loop, _, condition, body}) do
1 + max(calculate_depth(condition), calculate_depth(body))
end
defp calculate_depth({:loop, _, iterator, collection, body}) do
1 + Enum.max([calculate_depth(iterator), calculate_depth(collection), calculate_depth(body)])
end
defp calculate_depth({:lambda, _params, _captures, body}) do
1 + calculate_depth(body)
end
defp calculate_depth({:collection_op, _, lambda, collection}) do
1 + max(calculate_depth(lambda), calculate_depth(collection))
end
defp calculate_depth({:collection_op, _, lambda, collection, initial}) do
1 + Enum.max([calculate_depth(lambda), calculate_depth(collection), calculate_depth(initial)])
end
defp calculate_depth({:language_specific, _, _, _}), do: 1
defp calculate_depth(_), do: 1
@spec count_nodes(AST.meta_ast()) :: non_neg_integer()
# Handle location-aware nodes
defp count_nodes({:literal, _, _, _loc}), do: 1
defp count_nodes({:literal, _, _}), do: 1
defp count_nodes({:variable, _, _loc}), do: 1
defp count_nodes({:variable, _}), do: 1
defp count_nodes({:binary_op, _, _, left, right, _loc}) do
1 + count_nodes(left) + count_nodes(right)
end
defp count_nodes({:binary_op, _, _, left, right}) do
1 + count_nodes(left) + count_nodes(right)
end
defp count_nodes({:unary_op, _, _, operand, _loc}) do
1 + count_nodes(operand)
end
defp count_nodes({:unary_op, _, _, operand}) do
1 + count_nodes(operand)
end
defp count_nodes({:function_call, _, args, _loc}) do
1 + Enum.sum(Enum.map(args, &count_nodes/1))
end
defp count_nodes({:function_call, _, args}) do
1 + Enum.sum(Enum.map(args, &count_nodes/1))
end
defp count_nodes({:conditional, condition, then_branch, else_branch}) do
base = 1 + count_nodes(condition) + count_nodes(then_branch)
if else_branch do
base + count_nodes(else_branch)
else
base
end
end
defp count_nodes({:block, statements}) do
1 + Enum.sum(Enum.map(statements, &count_nodes/1))
end
defp count_nodes({:list, elements}) do
1 + Enum.sum(Enum.map(elements, &count_nodes/1))
end
defp count_nodes({:map, pairs}) do
1 + Enum.sum(Enum.flat_map(pairs, fn {k, v} -> [count_nodes(k), count_nodes(v)] end))
end
defp count_nodes({:loop, _, condition, body}) do
1 + count_nodes(condition) + count_nodes(body)
end
defp count_nodes({:loop, _, iterator, collection, body}) do
1 + count_nodes(iterator) + count_nodes(collection) + count_nodes(body)
end
defp count_nodes({:lambda, _params, _captures, body}) do
1 + count_nodes(body)
end
defp count_nodes({:collection_op, _, lambda, collection}) do
1 + count_nodes(lambda) + count_nodes(collection)
end
defp count_nodes({:collection_op, _, lambda, collection, initial}) do
1 + count_nodes(lambda) + count_nodes(collection) + count_nodes(initial)
end
defp count_nodes({:language_specific, _, _, _}), do: 1
defp count_nodes(_), do: 1
@spec detect_layer(AST.meta_ast()) :: layer()
defp detect_layer(ast) do
if contains_native?(ast) do
:native
else
if contains_extended?(ast) do
:extended
else
:core
end
end
end
@spec contains_native?(AST.meta_ast()) :: boolean()
defp contains_native?({:language_specific, _, _, _}), do: true
defp contains_native?({:binary_op, _, _, left, right}) do
contains_native?(left) || contains_native?(right)
end
defp contains_native?({:block, statements}) do
Enum.any?(statements, &contains_native?/1)
end
defp contains_native?(_), do: false
@spec contains_extended?(AST.meta_ast()) :: boolean()
defp contains_extended?({:loop, _, _, _}), do: true
defp contains_extended?({:loop, _, _, _, _}), do: true
defp contains_extended?({:lambda, _, _, _}), do: true
defp contains_extended?({:collection_op, _, _, _}), do: true
defp contains_extended?({:collection_op, _, _, _, _}), do: true
defp contains_extended?({:exception_handling, _, _, _}), do: true
defp contains_extended?({:binary_op, _, _, left, right}) do
contains_extended?(left) || contains_extended?(right)
end
defp contains_extended?({:block, statements}) do
Enum.any?(statements, &contains_extended?/1)
end
defp contains_extended?(_), do: false
end