init commit
This commit is contained in:
+119
-33
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user