Metastatic.Builder (Metastatic v0.10.4)

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

Build a MetaAST document from a file.

Build a MetaAST document from source code.

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

Get information about available language adapters.

Write a MetaAST document to a file.

Convert a MetaAST document back to source code.

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