init commit

This commit is contained in:
2026-03-09 23:09:46 -04:00
parent 5cbc493cc5
commit 5da77e3360
73 changed files with 9935 additions and 103 deletions
+119 -33
View File
@@ -53,14 +53,51 @@ defmodule CljElixir.Transformer do
def transform(forms, ctx \\ %Context{})
def transform(forms, ctx) when is_list(forms) do
{elixir_forms, _ctx} =
Enum.map_reduce(forms, ctx, fn form, acc ->
{ast, new_ctx} = transform_form(form, acc)
{ast, new_ctx}
# Check if file has explicit defmodule forms (ns won't auto-wrap if so)
has_defmodule =
Enum.any?(forms, fn
{:list, _, [{:symbol, _, "defmodule"} | _]} -> true
_ -> false
end)
# Filter out nil (from defmacro which produces no runtime code)
elixir_forms = Enum.filter(elixir_forms, &(&1 != nil))
{elixir_forms, final_ctx} =
Enum.map_reduce(forms, ctx, fn form, acc ->
{ast, new_ctx} = transform_form(form, acc)
# Tag each transformed form with whether the source was a def-like form
{{ast, def_form?(form)}, new_ctx}
end)
# Filter out nil (from ns, defmacro which produce no runtime code)
elixir_forms = Enum.filter(elixir_forms, fn {ast, _} -> ast != nil end)
# If ns declared a module and there are no explicit defmodule forms,
# separate def-forms (inside module) from expressions (after module)
elixir_forms =
if final_ctx.module_name != nil and ctx.module_name == nil and not has_defmodule do
{defs, exprs} =
Enum.split_with(elixir_forms, fn {_ast, is_def} -> is_def end)
def_asts = Enum.map(defs, fn {ast, _} -> ast end)
expr_asts = Enum.map(exprs, fn {ast, _} -> ast end)
block =
case def_asts do
[] -> nil
[single] -> single
multiple -> {:__block__, [], multiple}
end
module_ast =
if block do
[{:defmodule, [context: Elixir], [final_ctx.module_name, [do: block]]}]
else
[]
end
module_ast ++ expr_asts
else
Enum.map(elixir_forms, fn {ast, _} -> ast end)
end
case elixir_forms do
[] -> nil
@@ -74,6 +111,30 @@ defmodule CljElixir.Transformer do
ast
end
# Transform a list of guard forms into a single ANDed Elixir guard AST.
# [:guard [(> x 0) (< x 10)]] → {:and, [], [guard1, guard2]}
defp transform_guards(guard_forms, ctx) do
guard_asts = Enum.map(guard_forms, &transform(&1, ctx))
case guard_asts do
[single] -> single
[first | rest] -> Enum.reduce(rest, first, fn g, acc ->
{:and, [context: Elixir], [acc, g]}
end)
end
end
# Is this CljElixir AST form a definition (goes inside defmodule)?
defp def_form?({:list, _, [{:symbol, _, name} | _]})
when name in ~w(defn defn- def defprotocol defrecord extend-type
extend-protocol reify defmacro use),
do: true
# m/=> schema annotations
defp def_form?({:list, _, [{:symbol, _, "m/=>"} | _]}), do: true
defp def_form?(_), do: false
# ---------------------------------------------------------------------------
# Main dispatch
# ---------------------------------------------------------------------------
@@ -258,6 +319,7 @@ defmodule CljElixir.Transformer do
defp transform_list([head | args], meta, ctx) do
case head do
# --- Special forms (symbols) ---
{:symbol, _, "ns"} -> transform_ns(args, meta, ctx)
{:symbol, _, "defmodule"} -> transform_defmodule(args, meta, ctx)
{:symbol, _, "defn"} -> transform_defn(args, meta, ctx, :def)
{:symbol, _, "defn-"} -> transform_defn(args, meta, ctx, :defp)
@@ -419,6 +481,17 @@ defmodule CljElixir.Transformer do
end
end
# ---------------------------------------------------------------------------
# 0. ns — module declaration (sets ctx.module_name for auto-wrapping)
# ---------------------------------------------------------------------------
defp transform_ns([name_form | _rest], _meta, ctx) do
mod_alias = module_name_ast(name_form)
{nil, %{ctx | module_name: mod_alias}}
end
defp transform_ns([], _meta, ctx), do: {nil, ctx}
# ---------------------------------------------------------------------------
# 1. defmodule
# ---------------------------------------------------------------------------
@@ -527,8 +600,8 @@ defmodule CljElixir.Transformer do
nil ->
{def_kind, em, [call_with_args(fun_name, param_asts), [do: body_ast]]}
guard_form ->
guard_ast = transform(guard_form, fn_ctx)
guard_forms ->
guard_ast = transform_guards(guard_forms, fn_ctx)
{def_kind, em,
[
@@ -568,18 +641,18 @@ defmodule CljElixir.Transformer do
{required, rest_param, nil, body}
{:list, _, clause_elements} ->
# Might have guard: ([params] :when guard body)
parse_clause_with_guard(clause_elements)
# Might have guard: ([params] :guard guard body)
parse_clause_with_guards(clause_elements)
end)
end
end
defp parse_clause_with_guard([{:vector, _, params}, :when, guard | body]) do
defp parse_clause_with_guards([{:vector, _, params}, :guard, {:vector, _, guards} | body]) do
{required, rest_param} = split_rest_params(params)
{required, rest_param, guard, body}
{required, rest_param, guards, body}
end
defp parse_clause_with_guard([{:vector, _, params} | body]) do
defp parse_clause_with_guards([{:vector, _, params} | body]) do
{required, rest_param} = split_rest_params(params)
{required, rest_param, nil, body}
end
@@ -630,8 +703,8 @@ defmodule CljElixir.Transformer do
nil ->
{:->, [], [all_param_asts, body_ast]}
guard_form ->
guard_ast = transform(guard_form, ctx)
guard_forms ->
guard_ast = transform_guards(guard_forms, ctx)
guard_params = [{:when, [], all_param_asts ++ [guard_ast]}]
{:->, [], [guard_params, body_ast]}
end
@@ -657,7 +730,7 @@ defmodule CljElixir.Transformer do
{required, rest_param, nil, body}
{:list, _, clause_elements} ->
parse_clause_with_guard(clause_elements)
parse_clause_with_guards(clause_elements)
end)
end
end
@@ -823,8 +896,8 @@ defmodule CljElixir.Transformer do
clauses
|> Enum.chunk_every(2)
|> Enum.map(fn
[pattern, :when | rest] ->
# pattern :when guard body — need to re-chunk
[pattern, :guard | rest] ->
# pattern :guard guard body — need to re-chunk
# This won't happen with chunk_every(2), handle differently
pat_ast = transform(pattern, pattern_ctx)
body_ast = transform(List.last(rest), ctx)
@@ -1550,8 +1623,8 @@ defmodule CljElixir.Transformer do
nil ->
{:->, [], [[pat_ast], body_ast]}
guard_form ->
guard_ast = transform(guard_form, ctx)
guard_forms ->
guard_ast = transform_guards(guard_forms, ctx)
{:->, [], [[{:when, [], [pat_ast, guard_ast]}], body_ast]}
end
end)
@@ -1585,8 +1658,8 @@ defmodule CljElixir.Transformer do
parse_receive_clauses(rest, acc, {timeout, body})
end
defp parse_receive_clauses([pattern, :when, guard, body | rest], acc, after_clause) do
parse_receive_clauses(rest, [{pattern, guard, body} | acc], after_clause)
defp parse_receive_clauses([pattern, :guard, {:vector, _, guards}, body | rest], acc, after_clause) do
parse_receive_clauses(rest, [{pattern, guards, body} | acc], after_clause)
end
defp parse_receive_clauses([pattern, body | rest], acc, after_clause) do
@@ -1959,23 +2032,24 @@ defmodule CljElixir.Transformer do
{{:-, [], [a_ast, 1]}, ctx}
end
# str — concatenate with <> using to_string
# str — Clojure-compatible: nil→"", strings pass through, collections use print repr
defp transform_str(args, ctx) do
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
str_call = fn arg ->
{{:., [], [{:__aliases__, [alias: false], [:CljElixir, :Printer]}, :str_value]}, [], [arg]}
end
ast =
case t_args do
[] ->
""
[single] ->
{{:., [], [{:__aliases__, [alias: false], [:Kernel]}, :to_string]}, [], [single]}
str_call.(single)
_ ->
stringified =
Enum.map(t_args, fn a ->
{{:., [], [{:__aliases__, [alias: false], [:Kernel]}, :to_string]}, [], [a]}
end)
stringified = Enum.map(t_args, str_call)
Enum.reduce(tl(stringified), hd(stringified), fn arg, acc ->
{:<>, [], [acc, arg]}
@@ -1985,12 +2059,12 @@ defmodule CljElixir.Transformer do
{ast, ctx}
end
# println → IO.puts
# println → IO.puts, returns nil (Clojure convention)
defp transform_println(args, ctx) do
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
# If multiple args, join with str first
ast =
io_call =
case t_args do
[single] ->
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], [single]}
@@ -2001,6 +2075,7 @@ defmodule CljElixir.Transformer do
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], [str_ast]}
end
ast = {:__block__, [], [io_call, nil]}
{ast, ctx}
end
@@ -2052,11 +2127,11 @@ defmodule CljElixir.Transformer do
end
end)
ast = {:__block__, [], writes}
ast = {:__block__, [], writes ++ [nil]}
{ast, ctx}
end
# (prn val) -> IO.puts(CljElixir.Printer.pr_str(val))
# (prn val) -> IO.puts(CljElixir.Printer.pr_str(val)), returns nil
# Multiple args joined with spaces, then newline
defp transform_prn(args, ctx) do
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
@@ -2079,7 +2154,8 @@ defmodule CljElixir.Transformer do
end)
end
ast = {{:., [], [io_mod, :puts]}, [], [joined]}
io_call = {{:., [], [io_mod, :puts]}, [], [joined]}
ast = {:__block__, [], [io_call, nil]}
{ast, ctx}
end
@@ -2535,6 +2611,16 @@ defmodule CljElixir.Transformer do
{ast, ctx}
end
# update - (update m k f x y ...) => rewrite to (assoc m k (f (get m k) x y ...))
# Rewritten at AST level so f goes through builtin dispatch (e.g. dissoc)
defp transform_update([m, k, f | extra_args], ctx) do
meta = %{line: 0, col: 0}
get_call = {:list, meta, [{:symbol, meta, "get"}, m, k]}
f_call = {:list, meta, [f, get_call | extra_args]}
assoc_call = {:list, meta, [{:symbol, meta, "assoc"}, m, k, f_call]}
do_transform(assoc_call, ctx)
end
# conj
defp transform_conj([c, x], ctx) do
c_ast = transform(c, ctx)