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 representation
  • metadata - Language-specific information preserved from M1
  • language - 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

Types

t()

A Document containing M2 AST with associated metadata.

Functions

Check if two documents have semantically equivalent ASTs.

Get the language of a 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

t()

@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

equivalent?(document1, document2)

@spec equivalent?(t(), t()) :: boolean()

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

language(document)

@spec language(t()) :: atom()

Get the language of a document.

Examples

iex> doc = Metastatic.Document.new({:literal, [subtype: :integer], 42}, :python)
iex> Metastatic.Document.language(doc)
:python

new(ast, language, metadata \\ %{}, original_source \\ nil)

@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(doc)

@spec normalize(t() | {atom(), term()}) :: {:ok, t()} | {:error, term()}

Normalize input to a Document.

Accepts either:

  • A Metastatic.Document struct (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)

@spec normalize!(t() | {atom(), term()}) :: t()

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

update_ast(doc, new_ast)

@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_metadata(doc, new_metadata)

@spec update_metadata(t(), map()) :: t()

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}

valid?(document)

@spec valid?(t()) :: boolean()

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

variables(document)

@spec variables(t()) :: MapSet.t(String.t())

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"])