Metastatic.Adapters.Python (Metastatic v0.10.4)

View Source

Python language adapter for MetaAST transformations.

Bridges between Python AST (M1) and MetaAST (M2), enabling cross-language code analysis and transformation for Python source code.

Python AST Structure (M1)

Python's ast module represents AST as nested dictionaries with a _type field:

%{"_type" => "BinOp", "op" => %{"_type" => "Add"}, "left" => ..., "right" => ...}

Examples

# Integer literal
%{"_type" => "Constant", "value" => 42}

# Variable
%{"_type" => "Name", "id" => "x", "ctx" => %{"_type" => "Load"}}

# Addition
%{"_type" => "BinOp",
  "op" => %{"_type" => "Add"},
  "left" => %{"_type" => "Name", "id" => "x"},
  "right" => %{"_type" => "Constant", "value" => 5}}

# Function call
%{"_type" => "Call",
  "func" => %{"_type" => "Name", "id" => "foo"},
  "args" => [...]}

M1 ↔ M2 Transformations

This adapter performs bidirectional transformations between Python AST (M1) and MetaAST (M2):

Literals

# M1 → M2
%{"_type" => "Constant", "value" => 42}     {:literal, :integer, 42}
%{"_type" => "Constant", "value" => 3.14}   {:literal, :float, 3.14}
%{"_type" => "Constant", "value" => "hi"}   {:literal, :string, "hi"}
%{"_type" => "Constant", "value" => true}   {:literal, :boolean, true}
%{"_type" => "Constant", "value" => nil}    {:literal, :null, nil}

Variables

%{"_type" => "Name", "id" => "x"}           {:variable, "x"}

Binary Operations

BinOp(op=Add())                             {:binary_op, :arithmetic, :+, left, right}
Compare(ops=[Eq()])                         {:binary_op, :comparison, :==, left, right}
BoolOp(op=And())                            {:binary_op, :boolean, :and, left, right}

Function Calls

Call(func=Name("foo"), args=[...])          {:function_call, "foo", [...]}

Conditionals

If(test=..., body=..., orelse=...)          {:conditional, test, body, orelse}
IfExp(test=..., body=..., orelse=...)       {:conditional, test, body, orelse}

Communication Strategy

Since Python parsing requires the ast module, this adapter uses subprocess communication:

  1. Parse: Spawn priv/parsers/python/parser.py → receives JSON AST
  2. Unparse: Spawn priv/parsers/python/unparser.py → receives source

All communication uses stdin/stdout with JSON serialization.

Round-Trip Fidelity

Target >90% round-trip fidelity (Python's AST is lossy for formatting). Metadata preserves:

  • Line numbers (lineno, col_offset)
  • End positions (end_lineno, end_col_offset)

Usage

# Parse Python source
{:ok, ast} = Metastatic.Adapters.Python.parse("x + 5")

# Transform to MetaAST
{:ok, meta_ast, metadata} = Metastatic.Adapters.Python.to_meta(ast)

# Transform back to Python AST
{:ok, ast2} = Metastatic.Adapters.Python.from_meta(meta_ast, metadata)

# Unparse to source
{:ok, source} = Metastatic.Adapters.Python.unparse(ast2)

Theory

This adapter implements the Galois connection:

α_Python: AS_Python  MetaAST × Metadata
ρ_Python: MetaAST × Metadata  AS_Python

Where:

  • α_Python is to_meta/1 (abstraction)
  • ρ_Python is from_meta/2 (reification)

Python and Elixir/Erlang produce semantically equivalent MetaAST for equivalent code:

# Python: x + 5
# Elixir: x + 5
# Erlang: X + 5.
# All → {:binary_op, :arithmetic, :+, {:variable, _}, {:literal, :integer, 5}}