Current section

Files

Jump to
metastatic lib metastatic analysis complexity nesting.ex
Raw

lib/metastatic/analysis/complexity/nesting.ex

defmodule Metastatic.Analysis.Complexity.Nesting do
@moduledoc """
Maximum nesting depth calculation.
Nesting depth measures how many levels deep the code is nested, which
affects readability and maintainability. Deeply nested code is harder
to understand and reason about.
## 3-Tuple Format
All MetaAST nodes use the uniform 3-tuple structure:
`{type_atom, keyword_meta, children_or_value}`
## What Increases Nesting
- `conditional` branches
- `loop` bodies
- `lambda` bodies
- `exception_handling` blocks
- Nested `block` structures (when inside above)
## Thresholds
- 0-2: Good, easy to understand
- 3-4: Moderate, acceptable
- 5+: High, should be refactored
## Examples
# No nesting: depth = 0
iex> ast = {:literal, [subtype: :integer], 42}
iex> Metastatic.Analysis.Complexity.Nesting.calculate(ast)
0
# Single conditional: depth = 1
iex> ast = {:conditional, [], [{:variable, [], "x"}, {:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> Metastatic.Analysis.Complexity.Nesting.calculate(ast)
1
# Nested conditional: depth = 2
iex> ast = {:conditional, [], [
...> {:variable, [], "x"},
...> {:conditional, [], [{:variable, [], "y"}, {:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]},
...> {:literal, [subtype: :integer], 3}]}
iex> Metastatic.Analysis.Complexity.Nesting.calculate(ast)
2
"""
alias Metastatic.AST
@doc """
Calculates maximum nesting depth for a MetaAST node.
Returns the depth as a non-negative integer (minimum 0).
## Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.Analysis.Complexity.Nesting.calculate(ast)
0
iex> ast = {:loop, [loop_type: :while], [{:variable, [], "condition"}, {:literal, [subtype: :integer], 1}]}
iex> Metastatic.Analysis.Complexity.Nesting.calculate(ast)
1
"""
@spec calculate(AST.meta_ast()) :: non_neg_integer()
def calculate(ast) do
{_current, max} = walk(ast, 0, 0)
max
end
# Private implementation
# walk(ast, current_depth, max_depth) -> {current_depth, max_depth}
# Conditional (3-tuple): increment depth for branches
defp walk({:conditional, _meta, [cond_expr, then_br, else_br]}, current, max) do
{_, max} = walk(cond_expr, current, max)
{_, max} = walk(then_br, current + 1, max)
{_, max} = walk(else_br, current + 1, max)
{current, max}
end
# Loop (3-tuple): increment depth for body
defp walk({:loop, meta, children}, current, max) when is_list(meta) do
loop_type = Keyword.get(meta, :loop_type)
case {loop_type, children} do
{:while, [cond_expr, body]} ->
{_, max} = walk(cond_expr, current, max)
{_, max} = walk(body, current + 1, max)
{current, max}
{_, [iter, coll, body]} ->
{_, max} = walk(iter, current, max)
{_, max} = walk(coll, current, max)
{_, max} = walk(body, current + 1, max)
{current, max}
_ ->
{_, max} =
Enum.reduce(children, {current, max}, fn child, {c, m} ->
walk(child, c + 1, m)
end)
{current, max}
end
end
# Binary operators (3-tuple): no depth change
defp walk({:binary_op, _meta, [left, right]}, current, max) do
{_, max} = walk(left, current, max)
{_, max} = walk(right, current, max)
{current, max}
end
# Unary operator (3-tuple)
defp walk({:unary_op, _meta, [operand]}, current, max) do
walk(operand, current, max)
end
# Exception handling (3-tuple): increment depth for blocks
defp walk({:exception_handling, _meta, [try_block, catches, else_block]}, current, max) do
{_, max} = walk(try_block, current + 1, max)
catches_list = if is_list(catches), do: catches, else: []
{_, max} =
Enum.reduce(catches_list, {current, max}, fn
{:catch_clause, _, [_type, _var, catch_body]}, {_c, m} ->
walk(catch_body, current + 1, m)
{_type, _var, catch_body}, {_c, m} ->
walk(catch_body, current + 1, m)
other, {_c, m} ->
walk(other, current + 1, m)
end)
{_, max} = walk(else_block, current + 1, max)
{current, max}
end
# Pattern match (3-tuple): increment depth for branches
defp walk({:pattern_match, _meta, [scrutinee | arms]}, current, max) when is_list(arms) do
{_, max} = walk(scrutinee, current, max)
{_, max} =
Enum.reduce(arms, {current, max}, fn
{:match_arm, _meta, body_list}, {_c, m} ->
Enum.reduce(body_list, {current, m}, fn child, {_cc, mm} ->
walk(child, current + 1, mm)
end)
other, {_c, m} ->
walk(other, current, m)
end)
{current, max}
end
# Match arm (3-tuple): increment depth for body
defp walk({:match_arm, _meta, body_list}, current, max) when is_list(body_list) do
Enum.reduce(body_list, {current, max}, fn child, {_c, m} ->
walk(child, current + 1, m)
end)
end
# Block (3-tuple): walk statements at same depth
defp walk({:block, _meta, stmts}, current, max) when is_list(stmts) do
max = Enum.max([current, max])
{_, max} =
Enum.reduce(stmts, {current, max}, fn stmt, {c, m} ->
walk(stmt, c, m)
end)
{current, max}
end
# Function call (3-tuple): walk arguments
defp walk({:function_call, _meta, args}, current, max) when is_list(args) do
{_, max} =
Enum.reduce(args, {current, max}, fn arg, {c, m} ->
walk(arg, c, m)
end)
{current, max}
end
# Lambda (3-tuple): increment depth for body
defp walk({:lambda, _meta, [body]}, current, max) do
{_, max} = walk(body, current + 1, max)
{current, max}
end
# M2.2s: Structural/Organizational Types (3-tuple format)
# Container: walk body at same depth
defp walk({:container, _meta, [body]}, current, max) do
max = Enum.max([current, max])
{_, max} =
if is_list(body) do
Enum.reduce(body, {current, max}, fn member, {c, m} ->
walk(member, c, m)
end)
else
walk(body, current, max)
end
{current, max}
end
# Function definition (3-tuple): walk body
defp walk({:function_def, meta, [body]}, current, max) when is_list(meta) do
params = Keyword.get(meta, :params, [])
# Walk parameters
{_, max} =
Enum.reduce(params, {current, max}, fn
{:param, meta, _name}, {c, m} when is_list(meta) ->
pattern = Keyword.get(meta, :pattern)
default = Keyword.get(meta, :default)
{_, m} = if pattern, do: walk(pattern, c, m), else: {c, m}
if default, do: walk(default, c, m), else: {c, m}
_simple_param, {c, m} ->
{c, m}
end)
# Walk guards if present
{_, max} =
case Keyword.get(meta, :guards) do
nil -> {current, max}
guard -> walk(guard, current, max)
end
# Walk body with incremented depth (function introduces scope)
walk(body, current + 1, max)
end
# Attribute access (3-tuple): walk object
defp walk({:attribute_access, _meta, [obj, _attr]}, current, max) do
walk(obj, current, max)
end
# Augmented assignment (3-tuple): walk value
defp walk({:augmented_assignment, _meta, [_target, value]}, current, max) do
walk(value, current, max)
end
# Property (3-tuple): walk getter and setter bodies
defp walk({:property, _meta, [getter, setter]}, current, max) do
{_, max} = if getter, do: walk(getter, current + 1, max), else: {current, max}
{_, max} = if setter, do: walk(setter, current + 1, max), else: {current, max}
{current, max}
end
# Collection operations (3-tuple)
defp walk({:collection_op, _meta, children}, current, max) when is_list(children) do
{_, max} =
Enum.reduce(children, {current, max}, fn child, {c, m} ->
walk(child, c, m)
end)
{current, max}
end
# Assignment (3-tuple)
defp walk({:assignment, _meta, [target, value]}, current, max) do
{_, max} = walk(target, current, max)
{_, max} = walk(value, current, max)
{current, max}
end
# Inline match (3-tuple)
defp walk({:inline_match, _meta, [pattern, value]}, current, max) do
{_, max} = walk(pattern, current, max)
{_, max} = walk(value, current, max)
{current, max}
end
# Early return (3-tuple)
defp walk({:early_return, _meta, [value]}, current, max) do
walk(value, current, max)
end
# List (3-tuple)
defp walk({:list, _meta, elems}, current, max) when is_list(elems) do
{_, max} =
Enum.reduce(elems, {current, max}, fn elem, {c, m} ->
walk(elem, c, m)
end)
{current, max}
end
# Map (3-tuple)
defp walk({:map, _meta, pairs}, current, max) when is_list(pairs) do
{_, max} =
Enum.reduce(pairs, {current, max}, fn
{:pair, _, [key, value]}, {c, m} ->
{_, m} = walk(key, c, m)
walk(value, c, m)
{key, value}, {c, m} ->
{_, m} = walk(key, c, m)
walk(value, c, m)
other, {c, m} ->
walk(other, c, m)
end)
{current, max}
end
# Async operation (3-tuple)
defp walk({:async_operation, _meta, [body]}, current, max) do
walk(body, current, max)
end
# Language-specific (3-tuple)
defp walk({:language_specific, meta, native_ast}, current, max) when is_list(meta) do
case native_ast do
%{body: body} when not is_nil(body) -> walk(body, current, max)
_ -> {current, max}
end
end
# Literals and variables (3-tuple): update max if current is higher
defp walk({:literal, _meta, _value}, current, max), do: {current, Enum.max([current, max])}
defp walk({:variable, _meta, _name}, current, max), do: {current, Enum.max([current, max])}
# Pair (3-tuple)
defp walk({:pair, _meta, [key, value]}, current, max) do
{_, max} = walk(key, current, max)
walk(value, current, max)
end
# Nil
defp walk(nil, current, max), do: {current, max}
# Fallback
defp walk(_, current, max), do: {current, max}
end