Packages
metastatic
0.26.0
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/subprocess.ex
defmodule Metastatic.Adapters.Python.Subprocess do
@moduledoc """
Subprocess management for Python parser and unparser.
Handles communication with Python scripts via stdin/stdout with JSON serialization.
"""
@parser_path "priv/parsers/python/parser.py"
@unparser_path "priv/parsers/python/unparser.py"
@doc """
Parse Python source code to AST JSON.
## Examples
iex> Metastatic.Adapters.Python.Subprocess.parse("x + 5")
{:ok, %{"_type" => "Module", "body" => [...]}}
iex> Metastatic.Adapters.Python.Subprocess.parse("x +")
{:error, "SyntaxError: ..."}
"""
@spec parse(String.t()) :: {:ok, map()} | {:error, String.t()}
def parse(source) do
case run_python_script(@parser_path, source) do
{:ok, result} ->
case Jason.decode(result) do
{:ok, %{"ok" => true, "ast" => ast}} ->
{:ok, ast}
{:ok, %{"ok" => false, "error" => error}} ->
{:error, format_error(error)}
{:error, reason} ->
{:error, "JSON decode failed: #{inspect(reason)}"}
end
{:error, reason} ->
{:error, "Python process failed: #{inspect(reason)}"}
end
end
@doc """
Unparse AST JSON back to Python source code.
## Examples
iex> ast = %{"_type" => "Module", "body" => [...]}
iex> Metastatic.Adapters.Python.Subprocess.unparse(ast)
{:ok, "x + 5"}
"""
@spec unparse(map()) :: {:ok, String.t()} | {:error, String.t()}
def unparse(ast) do
case Jason.encode(ast) do
{:ok, json} ->
case run_python_script(@unparser_path, json) do
{:ok, result} ->
case Jason.decode(result) do
{:ok, %{"ok" => true, "source" => source}} ->
{:ok, source}
{:ok, %{"ok" => false, "error" => error}} ->
{:error, format_error(error)}
{:error, reason} ->
{:error, "JSON decode failed: #{inspect(reason)}"}
end
{:error, reason} ->
{:error, "Python process failed: #{inspect(reason)}"}
end
{:error, reason} ->
{:error, "JSON encode failed: #{inspect(reason)}"}
end
end
# Private Functions
defp run_python_script(script_path, input) do
script_full_path = Path.join(File.cwd!(), script_path)
# Check if script exists
if File.exists?(script_full_path) do
temp_path =
Path.join(
System.tmp_dir!(),
"metastatic_py_#{System.unique_integer([:positive, :monotonic])}.txt"
)
File.write!(temp_path, input)
try do
case System.cmd("python3", [script_full_path, temp_path], stderr_to_stdout: true) do
{output, 0} ->
{:ok, output}
{error_output, exit_code} ->
{:error, "Exit code #{exit_code}: #{error_output}"}
end
after
File.rm(temp_path)
end
else
{:error, "Python script not found: #{script_full_path}"}
end
rescue
e ->
{:error, "Exception: #{Exception.message(e)}"}
end
defp format_error(%{"type" => type, "msg" => msg, "lineno" => line}) do
"#{type} at line #{line}: #{msg}"
end
defp format_error(%{"type" => type, "msg" => msg}) do
"#{type}: #{msg}"
end
defp format_error(error) when is_binary(error) do
error
end
defp format_error(error) do
inspect(error)
end
end