Current section

Files

Jump to
metastatic METAST_SPEC.md
Raw

METAST_SPEC.md

# MetaAST Format Specification
The MetaAST (Meta-level Abstract Syntax Tree) is a unified intermediate representation for programming language constructs, organized into three hierarchical layers.
## Meta-Modeling Hierarchy
MetaAST operates at the **M2 (meta-model)** level in a four-level hierarchy:
- **M3**: Elixir type system (`@type`, `@spec`) - defines what types CAN be
- **M2**: MetaAST (this specification) - defines what AST nodes CAN be
- **M1**: Language-specific ASTs (Python AST, JavaScript AST, Elixir AST) - what specific code IS
- **M0**: Runtime execution - what code DOES
Different M1 models (language ASTs) can be instances of the same M2 concept. For example:
```elixir
# M2 (meta-level representation):
{:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}
# M1 instances (language-specific):
Python: BinOp(op=Add(), left=Name('x'), right=Num(5))
JavaScript: BinaryExpression(operator: '+', left: Identifier('x'), right: Literal(5))
Elixir: {:+, [], [{:x, [], nil}, 5]}
```
All three M1 representations map to the identical M2 MetaAST, enabling cross-language semantic equivalence.
## Three-Layer Architecture
### M2.1: Core Layer
**Universal concepts present in ALL languages**
Always normalized to common representation without hints.
#### Literal
```elixir
{:literal, semantic_type, value}
```
**Semantic types**: `:integer`, `:float`, `:string`, `:boolean`, `:null`, `:symbol`, `:regex`, `:collection`
**Examples**:
```elixir
{:literal, :integer, 42}
{:literal, :string, "hello"}
{:literal, :boolean, true}
{:literal, :null, nil}
```
#### Variable
```elixir
{:variable, name}
```
**Example**: `{:variable, "x"}`
#### List
```elixir
{:list, elements_list}
```
Lists are ordered sequences of elements, fundamental data structures present in all programming languages.
**M1 instances**:
- Python: `ast.List`
- JavaScript: `Array`
- Elixir: list literal `[1, 2, 3]`
- Ruby: `Array`
- Erlang: list
**Examples**:
```elixir
{:list, []}
{:list, [{:literal, :integer, 1}, {:literal, :integer, 2}]}
{:list, [{:variable, "x"}, {:variable, "y"}]}
```
#### Map
```elixir
{:map, pairs_list}
```
Maps are key-value mappings, fundamental data structures present in all modern programming languages. Each pair is a 2-tuple `{key_ast, value_ast}`.
**M1 instances**:
- Python: `ast.Dict`
- JavaScript: `Object` literal
- Elixir: map `%{key => value}`
- Ruby: `Hash`
- Erlang: map
**Examples**:
```elixir
{:map, []}
{:map, [{{:literal, :string, "name"}, {:literal, :string, "Alice"}}]}
{:map, [{{:variable, "key"}, {:variable, "value"}}]}
```
#### Binary Operation
```elixir
{:binary_op, category, operator, left_ast, right_ast}
```
**Categories**: `:arithmetic`, `:comparison`, `:boolean`
**Examples**:
```elixir
{:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}
{:binary_op, :comparison, :>, {:variable, "age"}, {:literal, :integer, 18}}
{:binary_op, :boolean, :and, condition1, condition2}
```
#### Unary Operation
```elixir
{:unary_op, category, operator, operand_ast}
```
**Categories**: `:arithmetic`, `:boolean`
**Examples**:
```elixir
{:unary_op, :arithmetic, :-, {:variable, "x"}}
{:unary_op, :boolean, :not, {:variable, "flag"}}
```
#### Function Call
```elixir
{:function_call, name, args_list}
```
**Example**:
```elixir
{:function_call, "add", [{:variable, "x"}, {:variable, "y"}]}
```
#### Conditional
```elixir
{:conditional, condition_ast, then_ast, else_ast_or_nil}
```
**Example**:
```elixir
{:conditional,
{:binary_op, :comparison, :>, {:variable, "x"}, {:literal, :integer, 0}},
{:literal, :string, "positive"},
{:literal, :string, "non-positive"}}
```
#### Early Return
```elixir
{:early_return, value_ast}
```
#### Block
```elixir
{:block, statements_list}
```
#### Assignment
**For imperative languages (Python, JavaScript, Ruby)**
```elixir
{:assignment, target_ast, value_ast}
```
Represents imperative binding/mutation where `=` is an assignment operator.
**Examples**:
```elixir
# x = 5
{:assignment, {:variable, "x"}, {:literal, :integer, 5}}
# x, y = 1, 2 (tuple unpacking)
{:assignment,
{:tuple, [{:variable, "x"}, {:variable, "y"}]},
{:tuple, [{:literal, :integer, 1}, {:literal, :integer, 2}]}}
```
#### Inline Match
**For declarative languages (Elixir, Erlang)**
```elixir
{:inline_match, pattern_ast, value_ast}
```
Represents pattern matching where `=` is a match operator. The left side is a pattern that must unify with the right side.
**Examples**:
```elixir
# x = 5 (Elixir/Erlang)
{:inline_match, {:variable, "x"}, {:literal, :integer, 5}}
# {x, y} = {1, 2}
{:inline_match,
{:tuple, [{:variable, "x"}, {:variable, "y"}]},
{:tuple, [{:literal, :integer, 1}, {:literal, :integer, 2}]}}
# [head | tail] = list
{:inline_match,
{:cons_pattern, {:variable, "head"}, {:variable, "tail"}},
{:variable, "list"}}
```
#### Wildcard Pattern
```elixir
:_
```
Represents a catch-all pattern in pattern matching.
#### Tuple
```elixir
{:tuple, elements_list}
```
Used in patterns and destructuring.
### M2.2: Extended Layer
**Common patterns present in MOST languages**
Normalized with optional hints to preserve language-specific nuances.
#### Loop
```elixir
# While loop (4-tuple)
{:loop, :while, condition_ast, body_ast}
# For/foreach loop (5-tuple)
{:loop, :for | :for_each, iterator_ast, collection_ast, body_ast}
```
**Examples**:
```elixir
{:loop, :while,
{:binary_op, :comparison, :>, {:variable, "x"}, {:literal, :integer, 0}},
{:block, [{:variable, "x"}]}}
{:loop, :for, {:variable, "item"}, {:variable, "items"}, body_ast}
```
#### Lambda
```elixir
{:lambda, params_list, captures_list, body_ast}
```
**Example**:
```elixir
{:lambda, ["x", "y"], [],
{:binary_op, :arithmetic, :+, {:variable, "x"}, {:variable, "y"}}}
```
#### Collection Operations
```elixir
# Map/filter (4-tuple)
{:collection_op, :map | :filter, function_ast, collection_ast}
# Reduce (5-tuple)
{:collection_op, :reduce, function_ast, collection_ast, initial_ast}
```
**Example**:
```elixir
{:collection_op, :map,
{:lambda, ["x"], [], {:binary_op, :arithmetic, :*, {:variable, "x"}, {:literal, :integer, 2}}},
{:variable, "numbers"}}
```
#### Pattern Match
```elixir
{:pattern_match, scrutinee_ast, arms_list}
```
Where each arm is `{pattern_ast, body_ast}`.
**Example**:
```elixir
{:pattern_match, {:variable, "value"},
[
{{:literal, :integer, 0}, {:literal, :string, "zero"}},
{{:literal, :integer, 1}, {:literal, :string, "one"}},
{:_, {:literal, :string, "other"}}
]}
```
#### Exception Handling
```elixir
{:exception_handling, try_block_ast, rescue_clauses_list, finally_block_ast_or_nil}
```
Where each rescue clause is `{exception_atom, var_ast, body_ast}`.
#### Async Operation
```elixir
{:async_operation, :await | :async, operation_ast}
```
### M2.2s: Structural/Organizational Layer
**Top-level constructs for organizing code**
#### Container
**For modules, classes, namespaces**
```elixir
{:container, container_type, name, metadata, members_list}
```
**Container types**: `:module`, `:class`, `:namespace`
**Metadata fields**:
- `:source_language` - atom (`:python`, `:elixir`, `:ruby`, etc.)
- `:has_state` - boolean (mutable state management)
- `:visibility` - `%{public: [{name, arity}], private: [...], protected: [...]}`
- `:superclass` - string (direct superclass name or nil)
- `:organizational_model` - `:oop` or `:fp`
- `:original_ast` - M1 AST for round-trip fidelity
- `:decorators` - list of decorator MetaAST nodes
- `:type_params` - list of generic type parameters
- `:module_attributes` - module-level attributes
- `:constructor` - constructor function reference
- `:is_nested` - boolean
**Examples**:
```elixir
# Python class
{:container, :class, "Calculator",
%{source_language: :python,
has_state: true,
visibility: %{public: [{"add", 2}], private: [{"_validate", 1}]},
superclass: "BaseCalculator",
organizational_model: :oop},
[function_def1, function_def2]}
# Elixir module
{:container, :module, "MyApp.Math",
%{source_language: :elixir,
has_state: false,
organizational_model: :fp},
[function_def1, function_def2]}
```
#### Function Definition
```elixir
{:function_def, visibility, name, params_list, metadata, body_ast}
```
**Visibility**: `:public`, `:private`, `:protected`
**Parameter types**:
- Simple: `"x"` (string)
- Pattern: `{:pattern, meta_ast}`
- Default: `{:default, "name", default_value_ast}`
**Metadata fields**:
- `:guards` - guard clause as MetaAST
- `:arity` - integer
- `:return_type` - type annotation
- `:decorators` - list of decorator nodes
- `:is_async` - boolean
- `:is_static` - boolean
- `:is_abstract` - boolean
- `:specs` - function specifications
- `:doc` - documentation string
- `:original_ast` - M1 AST
**Examples**:
```elixir
# def add(x, y), do: x + y
{:function_def, :public, "add", ["x", "y"],
%{arity: 2},
{:binary_op, :arithmetic, :+, {:variable, "x"}, {:variable, "y"}}}
# def positive?(x) when x > 0
{:function_def, :public, "positive?", ["x"],
%{arity: 1, guards: {:binary_op, :comparison, :>, {:variable, "x"}, {:literal, :integer, 0}}},
{:literal, :boolean, true}}
# With default parameter
{:function_def, :public, "greet", [{:default, "name", {:literal, :string, "World"}}],
%{arity: 1},
{:function_call, "puts", [{:literal, :string, "Hello"}]}}
```
#### Attribute Access
```elixir
{:attribute_access, receiver_ast, attribute_name}
```
**Examples**:
```elixir
# obj.value
{:attribute_access, {:variable, "obj"}, "value"}
# user.address.street (chained)
{:attribute_access,
{:attribute_access, {:variable, "user"}, "address"},
"street"}
```
#### Augmented Assignment
**Preserves compound operators in non-desugared form**
```elixir
{:augmented_assignment, operator, target_ast, value_ast}
```
**Examples**:
```elixir
# x += 5
{:augmented_assignment, :+, {:variable, "x"}, {:literal, :integer, 5}}
# count *= 2
{:augmented_assignment, :*, {:variable, "count"}, {:literal, :integer, 2}}
```
#### Property
**For getter/setter properties**
```elixir
{:property, name, getter_function_def_or_nil, setter_function_def_or_nil, metadata}
```
**Metadata fields**:
- `:original_ast` - M1 AST
- `:is_read_only` - boolean
- `:is_write_only` - boolean
- `:backing_field` - backing field name
**Example**:
```elixir
# Ruby attr_reader (read-only)
{:property, "name",
{:function_def, :public, "name", [], %{}, {:variable, "@name"}},
nil,
%{is_read_only: true}}
```
### M2.3: Native Layer
**Language-specific escape hatches**
When M1 constructs cannot be abstracted to M2, they're preserved directly with semantic hints.
```elixir
# 5-tuple with embedded metadata (preferred)
{:language_specific, language_atom, native_info_map, hint_atom, metadata_map}
# 4-tuple without embedded metadata
{:language_specific, language_atom, native_info_map, hint_atom}
# 3-tuple (legacy format)
{:language_specific, language_atom, native_info_map}
```
**Example**:
```elixir
{:language_specific, :python,
%{construct: :list_comprehension, data: "[x for x in range(10)]"},
:functional_transform,
%{}}
```
## Helper Functions
The `Metastatic.AST` module provides utility functions:
```elixir
# Conformance validation
AST.conforms?(ast) # => true | false
AST.conforms?({:list, [{:variable, "x"}]}) # => true
AST.conforms?({:map, [{{:literal, :string, "k"}, {:variable, "v"}}]}) # => true
# Variable extraction
AST.variables(ast) # => MapSet.new(["x", "y"])
AST.variables({:list, [{:variable, "a"}, {:variable, "b"}]}) # => MapSet.new(["a", "b"])
AST.variables({:map, [{{:variable, "k"}, {:variable, "v"}}]}) # => MapSet.new(["k", "v"])
# Container queries
AST.container_name(container_ast) # => "MyApp.Math"
AST.has_state?(container_ast) # => true | false
# Function queries
AST.function_name(function_def_ast) # => "add"
AST.function_visibility(function_def_ast) # => :public
```
## Semantic Equivalence Principle
Different language ASTs that represent the same semantic concept produce identical MetaAST:
```
Python: x + 5 → M2: {:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}
JavaScript: x + 5 → M2: {:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}
Elixir: x + 5 → M2: {:binary_op, :arithmetic, :+, {:variable, "x"}, {:literal, :integer, 5}}
```
This enables:
- Universal transformations at M2 level
- Cross-language analysis tools
- Language-agnostic mutation testing
- Semantic equivalence validation