Packages
metastatic
0.10.2
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.ex
defmodule Metastatic do
@moduledoc """
Metastatic - Cross-language code analysis via unified MetaAST.
## Main API
The primary entry points for working with Metastatic:
- `quote/2` - Convert source code to MetaAST
- `unquote/2` - Convert MetaAST back to source code
## Examples
# Parse Python code to MetaAST
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :python)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
# Convert MetaAST back to Python source
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "x + 5"}
# Round-trip demonstration
iex> {:ok, my_ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, source} = Metastatic.unquote(my_ast, :python)
iex> source
"x + 5"
## Cross-Language Translation
Since MetaAST is language-independent, you can parse in one language
and generate in another:
# Parse from Python, generate Elixir code (same MetaAST!)
iex> {:ok, py_ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, _source} = Metastatic.unquote(py_ast, :elixir)
iex> true
true
"""
alias Metastatic.{Builder, Document}
@type language :: :elixir | :erlang | :ruby | :haskell | :python
@type meta_ast :: Metastatic.AST.meta_ast()
@languages ~w|elixir erlang ruby haskell python|a
@doc false
def languages, do: @languages
@doc false
def supported?(lang) when lang in @languages, do: true
def supported?(_), do: false
@doc false
def adapter_for_language(language)
for lang <- @languages do
mod = Module.concat([Metastatic.Adapters, lang |> Atom.to_string() |> Macro.camelize()])
def adapter_for_language(unquote(lang)), do: {:ok, unquote(mod)}
end
def adapter_for_language(lang),
do: {:error, {:unsupported_language, "No adapter found for language: #{inspect(lang)}"}}
@doc """
Convert source code to MetaAST.
Performs Source → M1 → M2 transformation, returning just the MetaAST
without the Document wrapper.
## Parameters
- `code` - Source code string
- `language` - Language atom (`:python`, `:elixir`, `:ruby`, `:erlang`, `:haskell`)
## Returns
- `{:ok, meta_ast}` - Successfully parsed and abstracted to M2
- `{:error, reason}` - Parsing or abstraction failed
## Examples
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :python)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
iex> {:ok, {:binary_op, meta, _}} = Metastatic.quote("x + 5", :elixir)
iex> Keyword.get(meta, :category) == :arithmetic and Keyword.get(meta, :operator) == :+
true
iex> {:ok, {:list, _, items}} = Metastatic.quote("[1, 2, 3]", :python)
iex> length(items) == 3
true
"""
@spec quote(String.t(), language()) :: {:ok, meta_ast()} | {:error, term()}
def quote(code, language) when is_binary(code) and is_atom(language) do
with {:ok, %Document{ast: ast}} <- Builder.from_source(code, language) do
{:ok, ast}
end
end
@doc """
Convert MetaAST to source code.
Performs M2 → M1 → Source transformation, generating source code in the
specified target language.
## Parameters
- `ast` - MetaAST structure (M2 level)
- `language` - Target language atom (`:python`, `:elixir`, `:ruby`, `:erlang`, `:haskell`)
## Returns
- `{:ok, source}` - Successfully generated source code
- `{:error, reason}` - Generation failed
## Examples
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "x + 5"}
iex> ast = {:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:literal, [subtype: :integer], 5}]}
iex> Metastatic.unquote(ast, :elixir)
{:ok, "x + 5"}
iex> ast = {:literal, [subtype: :integer], 42}
iex> Metastatic.unquote(ast, :python)
{:ok, "42"}
iex> ast = {:list, [], [{:literal, [subtype: :integer], 1}, {:literal, [subtype: :integer], 2}]}
iex> Metastatic.unquote(ast, :python)
{:ok, "[1, 2]"}
## Cross-Language Translation
Since MetaAST is language-independent, you can parse from one language
and generate in another:
iex> {:ok, ast} = Metastatic.quote("x + 5", :python)
iex> {:ok, source} = Metastatic.unquote(ast, :elixir)
iex> source
"x + 5"
iex> {:ok, ast} = Metastatic.quote("42", :ruby)
iex> {:ok, source} = Metastatic.unquote(ast, :python)
iex> source
"42"
"""
@spec unquote(meta_ast(), language()) :: {:ok, String.t()} | {:error, term()}
def unquote(ast, language) when is_atom(language) do
# Create minimal document wrapper (language will be used for target)
doc = %Document{ast: ast, language: language, metadata: %{}, original_source: nil}
Builder.to_source(doc)
end
end