Search documentation of Metastatic

# Metastatic.Builder (Metastatic v0.13.2)

View Source

High-level API for building MetaAST documents from source code.

This module provides the primary interface for users to work with Metastatic:

- `from_source/2` - Parse source code to MetaAST (Source → M1 → M2)
- `to_source/1` - Convert MetaAST back to source (M2 → M1 → Source)

## Usage

    # Parse Python code to MetaAST
    {:ok, doc} = Metastatic.Builder.from_source("x + 5", :python)

    # Doc now contains M2 representation (3-tuple format):
    doc.ast
    # => {:binary_op, [category: :arithmetic, operator: :+],
    #     [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}

    # Convert back to source
    {:ok, source} = Metastatic.Builder.to_source(doc)
    # => "x + 5"

## Round-Trip Example

    source = "x + 5"
    {:ok, doc} = Metastatic.Builder.from_source(source, :python)
    {:ok, result} = Metastatic.Builder.to_source(doc)

    # Should be semantically equivalent (may have normalized formatting)
    assert normalize(result) == normalize(source)

## Cross-Language Transformation

    # Parse from Python
    {:ok, doc} = Metastatic.Builder.from_source("x + 5", :python)

    # The M2 AST is language-independent!
    # Could theoretically transform to JavaScript, Elixir, etc.
    # (Once those adapters are implemented)

## Summary

### Functions

from_file(file_path, language \\ nil)

Build a MetaAST document from a file.

from_source(source, language)

Build a MetaAST document from source code.

round_trip(source, language)

Round-trip test: Source → M1 → M2 → M1 → Source.

supported_languages()

Get information about available language adapters.

to_file(document, file_path, target_language \\ nil)

Write a MetaAST document to a file.

to_source(document, target_language \\ nil)

Convert a MetaAST document back to source code.

valid_source?(source, language)

Validate that source code can be parsed and abstracted.

## Functions

# from_file(file_path, language \\ nil)

    @spec from_file(Path.t(), atom() | nil) ::
      {:ok, Metastatic.Document.t()} | {:error, term()}

Build a MetaAST document from a file.

Reads the file, detects language from extension, and parses to MetaAST.

## Parameters

- `file_path` - Path to source file
- `language` - Optional language override (auto-detected if not provided)

## Examples

    iex> Metastatic.Builder.from_file("script.py")
    {:ok, %Metastatic.Document{...}}

    iex> Metastatic.Builder.from_file("module.js")
    {:ok, %Metastatic.Document{language: :javascript, ...}}

    iex> Metastatic.Builder.from_file("nonexistent.py")
    {:error, :enoent}

# from_source(source, language)

    @spec from_source(String.t(), atom()) ::
      {:ok, Metastatic.Document.t()} | {:error, term()}

Build a MetaAST document from source code.

Performs the full Source → M1 → M2 pipeline:

1. Detects or uses specified language
2. Parses source to M1 (native AST)
3. Abstracts M1 to M2 (MetaAST)
4. Returns Document with M2 AST and metadata

## Parameters

- `source` - Source code string
- `language` - Language atom (`:python`, `:javascript`, `:elixir`, etc.) If not provided, attempts to detect from source (not yet implemented)

## Returns

- `{:ok, document}` - Successfully parsed and abstracted
- `{:error, reason}` - Parsing or abstraction failed

## Examples

    # Example result (not runnable - depends on Python adapter)
    # Metastatic.Builder.from_source("x + 5", :python)
    # => {:ok, %Metastatic.Document{
    #   ast: {:binary_op, [category: :arithmetic, operator: :+],
    #         [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]},
    #   language: :python,
    #   metadata: %{...},
    #   original_source: "x + 5"
    # }}

# round_trip(source, language)

    @spec round_trip(String.t(), atom()) :: {:ok, String.t()} | {:error, term()}

Round-trip test: Source → M1 → M2 → M1 → Source.

Useful for validating adapter fidelity. The result may have normalized formatting but should be semantically equivalent to the input.

## Examples

    iex> Metastatic.Builder.round_trip("x + 5", :python)
    {:ok, "x + 5"}

    iex> Metastatic.Builder.round_trip("x  +  5", :python)
    {:ok, "x + 5"}  # Normalized spacing

    iex> Metastatic.Builder.round_trip("invalid!", :python)
    {:error, "SyntaxError: ..."}

# supported_languages()

    @spec supported_languages() :: [map()]

Get information about available language adapters.

Returns a list of supported languages with their adapters.

## Examples

    iex> Metastatic.Builder.supported_languages()
    [
      %{language: :python, adapter: Metastatic.Adapters.Python, extensions: [".py"]},
      %{language: :javascript, adapter: Metastatic.Adapters.JavaScript, extensions: [".js", ".jsx"]}
    ]

# to_file(document, file_path, target_language \\ nil)

    @spec to_file(Metastatic.Document.t(), Path.t(), atom() | nil) ::
      :ok | {:error, term()}

Write a MetaAST document to a file.

Converts to source and writes to the specified path.

## Examples

    iex> doc = %Metastatic.Document{...}
    iex> Metastatic.Builder.to_file(doc, "output.py")
    :ok

    iex> Metastatic.Builder.to_file(doc, "/invalid/path/file.py")
    {:error, :enoent}

# to_source(document, target_language \\ nil)

    @spec to_source(Metastatic.Document.t(), atom() | nil) ::
      {:ok, String.t()} | {:error, term()}

Convert a MetaAST document back to source code.

Performs the full M2 → M1 → Source pipeline:

1. Gets adapter for document's language
2. Reifies M2 to M1 (native AST)
3. Unparses M1 to source
4. Returns source string

## Parameters

- `document` - MetaAST document
- `target_language` - Optional target language (defaults to document's language)

## Returns

- `{:ok, source}` - Successfully unparsed
- `{:error, reason}` - Unparsing failed

## Examples

    iex> doc = %Metastatic.Document{
    ...>   ast: {:literal, [subtype: :integer], 42},
    ...>   language: :elixir,
    ...>   metadata: %{}
    ...> }
    iex> Metastatic.Builder.to_source(doc)
    {:ok, "42"}

## Cross-Language Translation (Future)

    # This will be possible once multiple adapters are implemented
    iex> doc = %Metastatic.Document{ast: ..., language: :python, ...}
    iex> Metastatic.Builder.to_source(doc, :javascript)
    {:ok, "// JavaScript equivalent"}

# valid_source?(source, language)

    @spec valid_source?(String.t(), atom()) :: boolean()

Validate that source code can be parsed and abstracted.

Returns `true` if the source is valid, `false` otherwise.

## Examples

    iex> Metastatic.Builder.valid_source?("x + 5", :python)
    true

    iex> Metastatic.Builder.valid_source?("x +", :python)
    false

    iex> Metastatic.Builder.valid_source?("code", :unknown)
    false