Search documentation of Metastatic

# Metastatic.Document (Metastatic v0.13.2)

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

equivalent?(document1, document2)

Check if two documents have semantically equivalent ASTs.

language(document)

Get the language of a document.

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

Create a new MetaAST document.

normalize(doc)

Normalize input to a Document.

normalize!(input)

Normalize input to a Document, raising on error.

update_ast(doc, new_ast)

Update the AST in a document while preserving metadata and language.

update_metadata(doc, new_metadata)

Update document metadata (merges with existing metadata).

valid?(document)

Validate that a document's AST conforms to M2 meta-model.

variables(document)

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