Metastatic.Document
(Metastatic v0.10.4)
View Source
A MetaAST Document wraps an M2 AST with metadata and language information.
This structure represents the result of abstraction (M1 → M2) and serves as input for reification (M2 → M1).
Fields
ast- The M2 MetaAST representationmetadata- Language-specific information preserved from M1language- Source language (:python, :javascript, :elixir, etc.)original_source- Optional: original source code (for debugging/comparison)
Metadata
Metadata contains M1-specific information that cannot be represented at M2 level:
- Formatting preferences (indentation, spacing)
- Comments and documentation
- Type annotations (TypeScript, Python type hints)
- Language-specific hints (async models, iterator styles, etc.)
This enables high-fidelity M2 → M1 round-trips while maintaining semantic equivalence.
Examples
# After abstraction from Python (new 3-tuple format)
%Metastatic.Document{
language: :python,
ast: {:binary_op, [category: :arithmetic, operator: :+],
[{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]},
metadata: %{
native_lang: :python,
type_hints: %{"x" => "int"},
formatting: %{indent: 4}
},
original_source: "x + 5"
}
Summary
Functions
Check if two documents have semantically equivalent ASTs.
Get the language of a document.
Create a new MetaAST document.
Normalize input to a Document.
Normalize input to a Document, raising on error.
Update the AST in a document while preserving metadata and language.
Update document metadata (merges with existing metadata).
Validate that a document's AST conforms to M2 meta-model.
Extract all variables referenced in the document's AST.
Types
@type t() :: %Metastatic.Document{ ast: Metastatic.AST.meta_ast(), language: atom(), metadata: map(), original_source: String.t() | nil }
A Document containing M2 AST with associated metadata.
Functions
Check if two documents have semantically equivalent ASTs.
This compares the M2 AST structures, ignoring metadata and original source.
Examples
iex> doc1 = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> doc2 = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :javascript)
iex> Metastatic.Document.equivalent?(doc1, doc2)
true
iex> doc1 = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> doc2 = Metastatic.Document.new({:literal, [subtype: :string], "42"}, :python)
iex> Metastatic.Document.equivalent?(doc1, doc2)
false
Get the language of a document.
Examples
iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> Metastatic.Document.language(doc)
:python
@spec new(Metastatic.AST.meta_ast(), atom(), map(), String.t() | nil) :: t()
Create a new MetaAST document.
Examples
iex> ast = {:literal, [subtype: :integer], 42}
iex> Metastatic.Document.new(ast, :python)
%Metastatic.Document{
ast: {:literal, [subtype: :integer], 42},
language: :python,
metadata: %{},
original_source: nil
}
iex> ast = {:variable, [], "x"}
iex> metadata = %{type_hint: "str"}
iex> Metastatic.Document.new(ast, :python, metadata, "x")
%Metastatic.Document{
ast: {:variable, [], "x"},
language: :python,
metadata: %{type_hint: "str"},
original_source: "x"
}
Normalize input to a Document.
Accepts either:
- A
Metastatic.Documentstruct (returned as-is) - A
{language, native_ast}tuple (converted to Document using the appropriate adapter)
This enables analyzers to accept both formats for convenience.
Examples
# Already a Document - returns as-is
iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> Metastatic.Document.normalize(doc)
{:ok, doc}
# {lang, native_ast} tuple - converts using adapter
# (Python adapter doctest skipped until adapter is updated to 3-tuple format)
# python_ast = %{"_type" => "Constant", "value" => 42}
# {:ok, doc} = Metastatic.Document.normalize({:python, python_ast})
# doc.ast => {:literal, [subtype: :integer], 42}
Normalize input to a Document, raising on error.
Examples
iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> Metastatic.Document.normalize!(doc)
doc
@spec update_ast(t(), Metastatic.AST.meta_ast()) :: t()
Update the AST in a document while preserving metadata and language.
Useful for transformations that operate on the M2 level.
Examples
iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> new_ast = {:literal, [subtype: :integer], 100}
iex> updated = Metastatic.Document.update_ast(doc, new_ast)
iex> updated.ast
{:literal, [subtype: :integer], 100}
iex> updated.language
:python
Update document metadata (merges with existing metadata).
Examples
iex> doc = Metastatic.Document.new({:variable, [], "x"}, :python, %{type: "int"})
iex> updated = Metastatic.Document.update_metadata(doc, %{mutable: false})
iex> updated.metadata
%{type: "int", mutable: false}
Validate that a document's AST conforms to M2 meta-model.
Examples
iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> Metastatic.Document.valid?(doc)
true
iex> doc = %Metastatic.Document{
...> ast: {:invalid_node, [], "data"},
...> language: :python,
...> metadata: %{}
...> }
iex> Metastatic.Document.valid?(doc)
false
Extract all variables referenced in the document's AST.
Delegates to Metastatic.AST.variables/1.
Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+],
...> [{:variable, [], "x"}, {:variable, [], "y"}]}
iex> doc = Metastatic.Document.new(ast, :python)
iex> Metastatic.Document.variables(doc)
MapSet.new(["x", "y"])