Packages
metastatic
0.4.1
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/adapters/python.ex
defmodule Metastatic.Adapters.Python do
@moduledoc """
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}}
"""
@behaviour Metastatic.Adapter
alias Metastatic.Adapters.Python.{FromMeta, Subprocess, ToMeta}
@impl true
def parse(source) when is_binary(source) do
Subprocess.parse(source)
end
@impl true
def to_meta(python_ast) do
ToMeta.transform(python_ast)
end
@impl true
def from_meta(meta_ast, metadata) do
FromMeta.transform(meta_ast, metadata)
end
@impl true
def unparse(python_ast) do
Subprocess.unparse(python_ast)
end
@impl true
def file_extensions do
[".py", ".pyw"]
end
end