<img src="https://raw.githubusercontent.com/Oeditus/metastatic/v0.13.0/stuff/img/logo-128x128.png" alt="Metastatic" width="128" align="right">
# Metastatic
**Cross-language code meta-model library using unified MetaAST representation**
Metastatic provides a unified MetaAST (Meta-level Abstract Syntax Tree) intermediate representation for parsing, transforming, and translating code across multiple programming languages using a three-layer meta-model architecture.
## Vision
Parse once, use everywhere. A universal meta-model for program syntax that enables cross-language code transformation and tooling.
**Metastatic provides the foundation** - the MetaAST meta-model and language adapters. Analysis tools are provided by the companion library [MetaCredo](https://github.com/Oeditus/metacredo).
## Key Features
- **Layered Architecture**: Three-layer MetaAST design (M2.1 Core, M2.2 Extended, M2.3 Native)
- **Language Adapters**: Bidirectional M1 ↔ M2 transformations for multiple languages
- **Round-Trip Fidelity**: Transform source → MetaAST → source with >90% accuracy
- **Meta-Model Foundation**: MOF-based meta-modeling (M2 level) for universal AST representation
- **Cross-Language Equivalence**: Semantically equivalent code produces identical MetaAST across languages
- **Semantic Enrichment**: OpKind metadata system for accurate operation detection (DB, HTTP, file, cache, auth, queue, external API)
## Scope
**What Metastatic Provides:**
- MetaAST meta-model (M2 level) with three layers
- Language adapters (Python, Elixir, Erlang, Ruby, Haskell)
- Parsing, transformation, and unparsing infrastructure
- Cross-language semantic equivalence validation
- Semantic enrichment (OpKind metadata)
**What Metastatic Does NOT Provide:**
- Static analysis (see [MetaCredo](https://github.com/Oeditus/metacredo))
Metastatic is a **foundation library** that other tools build upon.
## Quick Start
### CLI Tools
Metastatic provides command-line tools for cross-language translation, AST inspection, and equivalence validation:
```bash
# Cross-language translation
mix metastatic.translate --from python --to elixir hello.py
mix metastatic.translate --from elixir --to python lib/module.ex --output py_output/
# AST inspection (tree format)
mix metastatic.inspect hello.py
# AST inspection (JSON format)
mix metastatic.inspect --format json hello.py
# Filter by layer
mix metastatic.inspect --layer core hello.py
# Extract variables only
mix metastatic.inspect --variables hello.py
# Check semantic equivalence
mix metastatic.validate_equivalence hello.py hello.ex
# Show detailed differences
mix metastatic.validate_equivalence --verbose file1.py file2.ex
```
### Using Language Adapters
Metastatic currently supports 5 language adapters: Python, Elixir, Erlang, Ruby, and Haskell.
#### Elixir & Erlang
```elixir
alias Metastatic.Adapters.{Elixir, Erlang}
alias Metastatic.{Adapter, Document}
# Parse Elixir source code
{:ok, doc} = Adapter.abstract(Elixir, "x + 5", :elixir)
doc.ast # => {:binary_op, [category: :arithmetic, operator: :+],
# [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
# Parse Erlang source code
{:ok, doc} = Adapter.abstract(Erlang, "X + 5.", :erlang)
doc.ast # => {:binary_op, [category: :arithmetic, operator: :+],
# [{:variable, [], "X"}, {:literal, [subtype: :integer], 5}]}
# Round-trip transformation
source = "x + y * 2"
{:ok, result} = Adapter.round_trip(Elixir, source)
result == source # => true
# Convert back to source
{:ok, source} = Adapter.reify(Elixir, doc)
# Cross-language equivalence
elixir_source = "x + 5"
erlang_source = "X + 5."
{:ok, elixir_doc} = Adapter.abstract(Elixir, elixir_source, :elixir)
{:ok, erlang_doc} = Adapter.abstract(Erlang, erlang_source, :erlang)
# Both produce semantically equivalent MetaAST!
# (only variable naming differs: "x" vs "X")
```
#### Python
```elixir
alias Metastatic.Adapters.Python
# Parse Python arithmetic
{:ok, doc} = Adapter.abstract(Python, "x + 5", :python)
doc.ast # => {:binary_op, [category: :arithmetic, operator: :+],
# [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
# Parse Python class
source = """
class Calculator:
def __init__(self, value=0):
self.value = value
def add(self, x):
self.value += x
return self
"""
{:ok, doc} = Adapter.abstract(Python, source, :python)
# doc.ast contains {:language_specific, :python, ...} for class definition
```
#### Ruby
```elixir
alias Metastatic.Adapters.Ruby
# Parse Ruby code
{:ok, doc} = Adapter.abstract(Ruby, "x + 5", :ruby)
doc.ast # => {:binary_op, [category: :arithmetic, operator: :+],
# [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
# Parse Ruby class with method chaining
source = """
class Calculator
attr_reader :value
def initialize(initial = 0)
@value = initial
end
def add(x)
@value += x
self
end
end
"""
{:ok, doc} = Adapter.abstract(Ruby, source, :ruby)
# doc.ast contains {:language_specific, :ruby, ...} for class definition
```
#### Haskell
```elixir
alias Metastatic.Adapters.Haskell
# Parse Haskell arithmetic
{:ok, doc} = Adapter.abstract(Haskell, "x + 5", :haskell)
doc.ast # => {:binary_op, [category: :arithmetic, operator: :+],
# [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
# Parse Haskell function with type signature
source = """
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
"""
{:ok, doc} = Adapter.abstract(Haskell, source, :haskell)
# doc.ast contains {:language_specific, :haskell, ...} for type signature and function
# Parse data type definition
source = "data Maybe a = Nothing | Just a"
{:ok, doc} = Adapter.abstract(Haskell, source, :haskell)
# doc.ast contains {:language_specific, :haskell, ...} for algebraic data type
```
### Working with MetaAST Directly
```elixir
alias Metastatic.{AST, Document, Validator}
# Create a MetaAST document (uniform 3-tuple format)
ast = {:binary_op, [category: :arithmetic, operator: :+],
[{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
doc = Document.new(ast, :elixir)
# Validate conformance
{:ok, meta} = Validator.validate(doc)
meta.level # => :core
meta.variables # => MapSet.new(["x"])
# Extract variables
AST.variables(ast) # => MapSet.new(["x"])
# Check conformance
AST.conforms?(ast) # => true
```
### AST Traversal & Manipulation
MetaAST trees need to be walked, searched, and transformed -- for refactoring,
linting, or building new cross-language tools. Metastatic provides a
full set of traversal and manipulation functions that mirror Elixir's `Macro` module,
adapted for the MetaAST 3-tuple format. All are available both on `Metastatic.AST`
(canonical) and as convenience wrappers on the `Metastatic` module itself.
#### Why traversal matters
Unlike Elixir's native AST, MetaAST nodes come from many languages. A single
traversal API means you write a variable renamer or a refactoring tool *once*
and it works on Python, Ruby, Erlang, Haskell, and Elixir code.
#### Walking the tree
```elixir
alias Metastatic.AST
{:ok, ast} = Metastatic.quote("x + y * 2", :python)
# Transform-only walk (no accumulator) -- like Macro.postwalk/2
new_ast = Metastatic.postwalk(ast, fn
{:variable, meta, name} -> {:variable, meta, String.upcase(name)}
node -> node
end)
# Walk with accumulator -- like Macro.prewalk/3
{_ast, var_names} = Metastatic.prewalk(ast, [], fn
{:variable, _, name} = node, acc -> {node, [name | acc]}
node, acc -> {node, acc}
end)
# var_names => ["y", "x"]
# Full pre+post traverse -- like Macro.traverse/4
{_ast, count} = Metastatic.traverse(ast, 0,
fn node, acc -> {node, acc + 1} end, # pre
fn node, acc -> {node, acc} end # post
)
```
#### Lazy enumeration
```elixir
# Stream all nodes depth-first -- like Macro.prewalker/1
ast |> Metastatic.prewalker() |> Enum.filter(&AST.operator?/1)
# Post-order stream -- like Macro.postwalker/1
ast |> Metastatic.postwalker() |> Enum.count()
```
#### Finding nodes
```elixir
# Path from a matching node up to the root -- like Macro.path/2
path = Metastatic.path(ast, fn
{:literal, _, 42} -> true
_ -> false
end)
# => [{:literal, ...42}, {:binary_op, ...}, ...root]
```
#### Pipe utilities
```elixir
# Decompose pipe chains -- like Macro.unpipe/1
steps = Metastatic.unpipe(pipe_ast)
# => [{initial_expr, 0}, {call1, 0}, {call2, 0}]
# Inject an expression into a function call -- like Macro.pipe/3
Metastatic.pipe_into(expr, call_node, 0)
```
#### Predicates and inspection
```elixir
# Is the whole subtree purely literal? -- like Macro.quoted_literal?/1
Metastatic.literal?({:list, [], [{:literal, [subtype: :integer], 1}]}) # => true
# Is it an operator node?
Metastatic.operator?(ast) # => true for :binary_op / :unary_op
# Human-readable representation -- like Macro.to_string/1
Metastatic.to_string(ast) # => "x + y * 2"
# Decompose a function call -- like Macro.decompose_call/1
Metastatic.decompose_call(call_node) # => {"add", [arg1, arg2]}
# Validate structure with diagnostics -- like Macro.validate/1
Metastatic.validate(ast) # => :ok | {:error, {:invalid_node, ...}}
# Generate a fresh variable for transformations -- like Macro.unique_var/2
Metastatic.unique_var("tmp") # => {:variable, [], "tmp_42"}
```
### Supplemental Modules
Supplemental modules extend MetaAST with library-specific integrations, enabling cross-language transformations:
```elixir
alias Metastatic.Supplemental.Transformer
# Transform actor patterns to Python Pykka library calls
ast = {:actor_call, {:variable, "worker"}, "process", [data]}
{:ok, python_ast} = Transformer.transform(ast, :python)
# Result: {:function_call, "worker.ask", [{:literal, :string, "process"}, data]}
# Check what supplementals are available for a language
Transformer.supported_constructs(:python)
# => [:actor_call, :actor_cast, :spawn_actor, :async_await, :async_context, :gather]
# Validate what supplementals a document needs
alias Metastatic.Supplemental.Validator
{:ok, analysis} = Validator.validate(doc)
analysis.required_supplementals # => [:pykka, :asyncio]
```
**Available supplementals:**
- **Python.Pykka** - Actor model support (`:actor_call`, `:actor_cast`, `:spawn_actor`)
- **Python.Asyncio** - Async/await patterns (`:async_await`, `:async_context`, `:gather`)
See **[Supplemental Modules](SUPPLEMENTAL_MODULES.md)** for comprehensive guide on using and creating supplementals.
## Documentation
- **[Theoretical Foundations](THEORETICAL_FOUNDATIONS.md)** - Formal meta-modeling theory and proofs
- **[Supplemental Modules](SUPPLEMENTAL_MODULES.md)** - Guide to using and creating supplemental modules
- **API Documentation** - Generate with `mix docs`
## Architecture
### Three-Layer MetaAST
**Layer 1: Core (M2.1)** - Universal concepts (ALL languages)
Common constructs: literals, variables, operators, conditionals, function calls, assignments
**Layer 2: Extended (M2.2)** - Common patterns (MOST languages)
Control flow: loops, lambdas, collection operations, pattern matching, exception handling
**Layer 2s: Structural/Organizational (M2.2s)** - Top-level constructs (MOST languages)
Organizational: containers (modules/classes/namespaces), function definitions, properties, attribute access, augmented assignments
**Layer 3: Native (M2.3)** - Language-specific escape hatches
Language-specific: lifetimes, async models, advanced type systems, metaprogramming
## Examples
### Shopping Cart Example
A comprehensive real-world example demonstrating metastatic's capabilities using an e-commerce shopping cart:
```bash
# From project root
mix compile
# Run interactive demo
elixir examples/shopping_cart/demo.exs
# Visualize MetaAST tree structures
elixir examples/shopping_cart/visualize_ast.exs
```
**What you'll learn:**
- How MetaAST represents real business logic (pricing, discounts, validation)
- Cross-language semantic equivalence (same logic in Python, JavaScript, Elixir, etc.)
- Three-layer architecture in practice (Core/Extended/Native)
**Files:**
- `examples/shopping_cart/README.md` - Comprehensive 500-line guide
- `examples/shopping_cart/lib/` - Product and Cart modules with rich business logic
- `examples/shopping_cart/demo.exs` - Interactive MetaAST operations demo
- `examples/shopping_cart/visualize_ast.exs` - Tree visualization with annotations
See [examples/README.md](examples/README.md) for more details.
## Use Cases
### Foundation for Cross-Language Tools
Metastatic provides the MetaAST foundation that other tools build upon:
```elixir
# Mutation testing (in muex library, NYI)
Muex.mutate_file("src/calculator.py", :python)
Muex.mutate_file("src/calculator.js", :javascript)
# Both use Metastatic's MetaAST under the hood!
```
### Cross-Language Code Transformation
Transform code between languages (for supported constructs):
```elixir
# Parse Python
{:ok, doc} = Metastatic.Builder.from_source(python_source, :python)
# Transform to Elixir (with supplemental modules for unsupported constructs)
{:ok, elixir_source} = Metastatic.Builder.to_source(doc, :elixir)
```
### Semantic Equivalence Validation
Verify that code across languages has identical semantics:
```elixir
{:ok, py_doc} = Metastatic.Builder.from_source("x + 5", :python)
{:ok, ex_doc} = Metastatic.Builder.from_source("x + 5", :elixir)
py_doc.ast == ex_doc.ast # => true (same MetaAST)
```
### AST Infrastructure
Build language-agnostic tools on top of MetaAST:
```elixir
# Extract all variables from any supported language
{:ok, doc} = Metastatic.Builder.from_source(source, language)
variables = Metastatic.AST.variables(doc.ast)
```
For static analysis, see [MetaCredo](https://github.com/Oeditus/metacredo) which provides 72 checks built on MetaAST.
## Contributing
This project is currently in the research/foundation phase. Contributions welcome!
## Research Background
Metastatic is inspired by research from:
- **muex** - Multi-language mutation testing analysis
- **propwise** - Property-based testing candidate identification
## Credits
Created as part of the Oeditus code quality tooling ecosystem.
Research synthesis from muex and propwise multi-language projects.
## Installation
```elixir
def deps do
[
{:metastatic, "~> 0.12"}
]
end
```
[Documentation](https://hexdocs.pm/metastatic).