Packages
metastatic
0.2.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/mix/tasks/metastatic.analyze.ex
defmodule Mix.Tasks.Metastatic.Analyze do
@shortdoc "Analyze MetaAST metrics and validate conformance"
@moduledoc """
Analyze MetaAST structure and provide detailed metrics.
## Usage
mix metastatic.analyze FILE [OPTIONS]
## Arguments
- `FILE` - Source file to analyze
## Options
- `--validate MODE` - Validation mode (strict, standard, permissive) [default: standard]
- `--language LANG` - Source language (optional, auto-detected from extension)
## Examples
Analyze a Python file with standard validation:
mix metastatic.analyze hello.py
Use strict validation (no language_specific nodes allowed):
mix metastatic.analyze hello.py --validate strict
Use permissive validation (allow all constructs):
mix metastatic.analyze hello.py --validate permissive
## Metrics Reported
- **Language** - Source language
- **Layer** - Deepest layer used (core, extended, native)
- **Depth** - Maximum AST depth
- **Node Count** - Total number of AST nodes
- **Variable Count** - Number of unique variables
- **Validation** - Conformance status and warnings
## Validation Modes
- **strict** - No language_specific nodes allowed, must be pure M2
- **standard** - Allow language_specific but warn (default)
- **permissive** - Allow all constructs without warnings
"""
use Mix.Task
alias Metastatic.{Builder, CLI, Validator}
alias Metastatic.CLI.Inspector
@switches [
validate: :string,
language: :string
]
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
{opts, paths, _invalid} = OptionParser.parse(args, strict: @switches)
case validate_args(opts, paths) do
{:ok, file_path, options} ->
analyze_file(file_path, options)
{:error, message} ->
CLI.fatal(message)
end
end
# Validation
@spec validate_args(keyword(), [String.t()]) ::
{:ok, String.t(), keyword()} | {:error, String.t()}
defp validate_args(opts, paths) do
with {:ok, file_path} <- validate_file_path(paths),
{:ok, language} <- validate_language(opts, file_path),
{:ok, validation_mode} <- validate_mode(opts) do
options = [
language: language,
validation_mode: validation_mode
]
{:ok, file_path, options}
end
end
@spec validate_file_path([String.t()]) :: {:ok, String.t()} | {:error, String.t()}
defp validate_file_path([]), do: {:error, "No source file provided"}
defp validate_file_path([path | _]) do
if File.exists?(path) && !File.dir?(path) do
{:ok, path}
else
{:error, "File does not exist or is a directory: #{path}"}
end
end
@spec validate_language(keyword(), String.t()) :: {:ok, atom()} | {:error, String.t()}
defp validate_language(opts, file_path) do
case Keyword.get(opts, :language) do
nil ->
case CLI.detect_language(file_path) do
{:ok, lang} -> {:ok, lang}
{:error, _} -> {:error, "Cannot detect language. Use --language option"}
end
lang_str ->
case String.downcase(lang_str) do
"python" -> {:ok, :python}
"elixir" -> {:ok, :elixir}
"erlang" -> {:ok, :erlang}
_ -> {:error, "Invalid language: #{lang_str}. Supported: python, elixir, erlang"}
end
end
end
@spec validate_mode(keyword()) :: {:ok, atom()} | {:error, String.t()}
defp validate_mode(opts) do
case Keyword.get(opts, :validate, "standard") do
"strict" ->
{:ok, :strict}
"standard" ->
{:ok, :standard}
"permissive" ->
{:ok, :permissive}
invalid ->
{:error, "Invalid validation mode: #{invalid}. Supported: strict, standard, permissive"}
end
end
# Analysis
@spec analyze_file(String.t(), keyword()) :: no_return()
defp analyze_file(file_path, options) do
language = Keyword.fetch!(options, :language)
validation_mode = Keyword.fetch!(options, :validation_mode)
with {:ok, source} <- CLI.read_file(file_path),
{:ok, doc} <- Builder.from_source(source, language),
{:ok, inspection} <- Inspector.inspect_document(doc),
{:ok, validation} <- Validator.validate(doc, mode: validation_mode) do
show_analysis(file_path, inspection, validation)
exit({:shutdown, 0})
else
{:error, reason} ->
CLI.fatal("Analysis failed: #{inspect(reason)}")
end
end
@spec show_analysis(String.t(), map(), map()) :: :ok
defp show_analysis(file_path, inspection, validation) do
Mix.shell().info(CLI.format_info("File: #{file_path}"))
Mix.shell().info("")
# Metrics section
Mix.shell().info("Metrics:")
Mix.shell().info(" Layer: #{inspection.layer}")
Mix.shell().info(" Depth: #{inspection.depth}")
Mix.shell().info(" Nodes: #{inspection.node_count}")
Mix.shell().info(" Variables: #{MapSet.size(inspection.variables)}")
Mix.shell().info("")
# Validation section
Mix.shell().info("Validation:")
Mix.shell().info(" Level: #{validation.level}")
Mix.shell().info(" Native Constructs: #{validation.native_constructs}")
if Enum.empty?(validation.warnings) do
Mix.shell().info(CLI.format_success(" No warnings"))
else
Mix.shell().info(" Warnings:")
Enum.each(validation.warnings, fn warning ->
Mix.shell().info(" - #{format_warning(warning)}")
end)
end
Mix.shell().info("")
# Summary
if validation.level == :core do
Mix.shell().info(CLI.format_success("Pure M2.1 (Core) - fully portable"))
else
if validation.level == :extended do
Mix.shell().info(CLI.format_success("M2.2 (Extended) - widely portable"))
else
Mix.shell().info(CLI.format_info("M2.3 (Native) - contains language-specific constructs"))
end
end
# Variable list if not too many
if MapSet.size(inspection.variables) > 0 && MapSet.size(inspection.variables) <= 20 do
Mix.shell().info("")
Mix.shell().info("Variables:")
inspection.variables
|> Enum.sort()
|> Enum.each(fn var ->
Mix.shell().info(" #{var}")
end)
end
end
@spec format_warning({atom(), any()}) :: String.t()
defp format_warning({:native_constructs_present, count}) do
"Contains #{count} language-specific construct(s)"
end
defp format_warning({:deep_nesting, depth}) do
"Deep nesting detected (depth: #{depth})"
end
defp format_warning({:large_ast, node_count}) do
"Large AST detected (#{node_count} nodes)"
end
defp format_warning(other) do
inspect(other)
end
end