Phase 8: REPL, printing, source maps, and nREPL server
- IPrintWithWriter protocol + CljElixir.Printer module with pr-str, print-str, pr, prn for all BEAM types (EDN-like output) - Source-mapped error messages: line/col metadata from reader now propagated through transformer into Elixir AST for accurate error locations in .clje files - Interactive REPL (mix clje.repl) with multi-line input detection, history, bindings persistence, and pr-str formatted output - nREPL server (mix clje.nrepl) with TCP transport, Bencode wire protocol, session management, and core operations (clone, close, eval, describe, ls-sessions, load-file, interrupt, completions). Writes .nrepl-port for editor auto-discovery. 92 new tests (699 total, 0 failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+202
-69
@@ -199,12 +199,12 @@ defmodule CljElixir.Transformer do
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Dynamic vars
|
||||
defp transform_symbol("*self*", _meta, ctx) do
|
||||
{{:self, [], []}, ctx}
|
||||
defp transform_symbol("*self*", meta, ctx) do
|
||||
{{:self, elixir_meta(meta), []}, ctx}
|
||||
end
|
||||
|
||||
defp transform_symbol("*node*", _meta, ctx) do
|
||||
{{:node, [], []}, ctx}
|
||||
defp transform_symbol("*node*", meta, ctx) do
|
||||
{{:node, elixir_meta(meta), []}, ctx}
|
||||
end
|
||||
|
||||
# true/false/nil are also possible as symbols
|
||||
@@ -213,10 +213,10 @@ defmodule CljElixir.Transformer do
|
||||
defp transform_symbol("nil", _meta, ctx), do: {nil, ctx}
|
||||
|
||||
# Module-qualified symbol like Enum/map or io/format
|
||||
defp transform_symbol(name, _meta, ctx) when is_binary(name) do
|
||||
defp transform_symbol(name, meta, ctx) when is_binary(name) do
|
||||
cond do
|
||||
String.contains?(name, "/") ->
|
||||
transform_module_call_symbol(name, ctx)
|
||||
transform_module_call_symbol(name, meta, ctx)
|
||||
|
||||
module_reference?(name) ->
|
||||
# Bare module reference (e.g., CljElixir.SubVector as a value)
|
||||
@@ -226,7 +226,7 @@ defmodule CljElixir.Transformer do
|
||||
# Plain variable
|
||||
munged = munge_name(name)
|
||||
atom_name = String.to_atom(munged)
|
||||
{{atom_name, [], nil}, ctx}
|
||||
{{atom_name, elixir_meta(meta), nil}, ctx}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -239,10 +239,11 @@ defmodule CljElixir.Transformer do
|
||||
String.contains?(name, ".")
|
||||
end
|
||||
|
||||
defp transform_module_call_symbol(name, ctx) do
|
||||
defp transform_module_call_symbol(name, meta, ctx) do
|
||||
{mod_ast, fun_atom} = parse_module_function(name)
|
||||
em = elixir_meta(meta)
|
||||
# Bare qualified symbol → zero-arg call (e.g., Enum/count as value)
|
||||
ast = {{:., [], [mod_ast, fun_atom]}, [], []}
|
||||
ast = {{:., em, [mod_ast, fun_atom]}, em, []}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -317,6 +318,10 @@ defmodule CljElixir.Transformer do
|
||||
{:symbol, _, "dec"} -> transform_dec(args, ctx)
|
||||
{:symbol, _, "str"} -> transform_str(args, ctx)
|
||||
{:symbol, _, "println"} -> transform_println(args, ctx)
|
||||
{:symbol, _, "pr-str"} -> transform_pr_str(args, ctx)
|
||||
{:symbol, _, "pr"} -> transform_pr(args, ctx)
|
||||
{:symbol, _, "prn"} -> transform_prn(args, ctx)
|
||||
{:symbol, _, "print-str"} -> transform_print_str(args, ctx)
|
||||
{:symbol, _, "nil?"} -> transform_nil_check(args, ctx)
|
||||
{:symbol, _, "throw"} -> transform_throw(args, ctx)
|
||||
{:symbol, _, "count"} -> transform_count(args, ctx)
|
||||
@@ -382,7 +387,7 @@ defmodule CljElixir.Transformer do
|
||||
|
||||
# --- Keyword-as-function: (:name user) ---
|
||||
kw when is_atom(kw) ->
|
||||
transform_keyword_call(kw, args, ctx)
|
||||
transform_keyword_call(kw, args, meta, ctx)
|
||||
|
||||
# --- Module/function calls from symbol with / ---
|
||||
{:symbol, _, name} when is_binary(name) ->
|
||||
@@ -390,18 +395,18 @@ defmodule CljElixir.Transformer do
|
||||
expand_macro(name, args, ctx)
|
||||
else
|
||||
if String.contains?(name, "/") do
|
||||
transform_module_call(name, args, ctx)
|
||||
transform_module_call(name, args, meta, ctx)
|
||||
else
|
||||
# Check for ->Constructor and map->Constructor
|
||||
cond do
|
||||
String.starts_with?(name, "->") and not String.starts_with?(name, "->>") ->
|
||||
transform_positional_constructor(name, args, ctx)
|
||||
transform_positional_constructor(name, args, meta, ctx)
|
||||
|
||||
String.starts_with?(name, "map->") ->
|
||||
transform_map_constructor(name, args, ctx)
|
||||
transform_map_constructor(name, args, meta, ctx)
|
||||
|
||||
true ->
|
||||
transform_unqualified_call(name, args, ctx)
|
||||
transform_unqualified_call(name, args, meta, ctx)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -418,7 +423,7 @@ defmodule CljElixir.Transformer do
|
||||
# 1. defmodule
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_defmodule(args, _meta, ctx) do
|
||||
defp transform_defmodule(args, meta, ctx) do
|
||||
{name_form, rest} = extract_name(args)
|
||||
mod_alias = module_name_ast(name_form)
|
||||
|
||||
@@ -459,7 +464,7 @@ defmodule CljElixir.Transformer do
|
||||
end
|
||||
|
||||
ast =
|
||||
{:defmodule, [context: Elixir],
|
||||
{:defmodule, [context: Elixir] ++ elixir_meta(meta),
|
||||
[mod_alias, [do: block]]}
|
||||
|
||||
{ast, ctx}
|
||||
@@ -469,7 +474,7 @@ defmodule CljElixir.Transformer do
|
||||
# 2. defn / defn-
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_defn(args, _meta, ctx, kind) do
|
||||
defp transform_defn(args, meta, ctx, kind) do
|
||||
{name_form, rest} = extract_name(args)
|
||||
fun_name = symbol_to_atom(name_form)
|
||||
|
||||
@@ -485,6 +490,7 @@ defmodule CljElixir.Transformer do
|
||||
clauses = parse_defn_clauses(rest)
|
||||
|
||||
def_kind = if kind == :def, do: :def, else: :defp
|
||||
em = elixir_meta(meta)
|
||||
|
||||
doc_ast =
|
||||
if doc do
|
||||
@@ -519,12 +525,12 @@ defmodule CljElixir.Transformer do
|
||||
clause =
|
||||
case guard do
|
||||
nil ->
|
||||
{def_kind, [], [call_with_args(fun_name, param_asts), [do: body_ast]]}
|
||||
{def_kind, em, [call_with_args(fun_name, param_asts), [do: body_ast]]}
|
||||
|
||||
guard_form ->
|
||||
guard_ast = transform(guard_form, fn_ctx)
|
||||
|
||||
{def_kind, [],
|
||||
{def_kind, em,
|
||||
[
|
||||
{:when, [],
|
||||
[call_with_args(fun_name, param_asts), guard_ast]},
|
||||
@@ -600,8 +606,9 @@ defmodule CljElixir.Transformer do
|
||||
# 3. fn
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_fn(args, _meta, ctx) do
|
||||
defp transform_fn(args, meta, ctx) do
|
||||
clauses = parse_fn_clauses(args)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
fn_clauses =
|
||||
Enum.flat_map(clauses, fn {params, rest_param, guard, body_forms} ->
|
||||
@@ -632,7 +639,7 @@ defmodule CljElixir.Transformer do
|
||||
[clause]
|
||||
end)
|
||||
|
||||
{{:fn, [], fn_clauses}, ctx}
|
||||
{{:fn, em, fn_clauses}, ctx}
|
||||
end
|
||||
|
||||
defp parse_fn_clauses(args) do
|
||||
@@ -739,15 +746,16 @@ defmodule CljElixir.Transformer do
|
||||
# 5. let
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_let([{:vector, _, bindings} | body], _meta, ctx) do
|
||||
defp transform_let([{:vector, _, bindings} | body], meta, ctx) do
|
||||
binding_pairs = Enum.chunk_every(bindings, 2)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
{binding_asts, final_ctx} =
|
||||
Enum.map_reduce(binding_pairs, ctx, fn [pattern, expr], acc ->
|
||||
pattern_ctx = %{acc | in_pattern: true}
|
||||
pat_ast = transform(pattern, pattern_ctx)
|
||||
expr_ast = transform(expr, acc)
|
||||
match_ast = {:=, [], [pat_ast, expr_ast]}
|
||||
match_ast = {:=, em, [pat_ast, expr_ast]}
|
||||
{match_ast, acc}
|
||||
end)
|
||||
|
||||
@@ -757,7 +765,7 @@ defmodule CljElixir.Transformer do
|
||||
ast =
|
||||
case all do
|
||||
[single] -> single
|
||||
multiple -> {:__block__, [], multiple}
|
||||
multiple -> {:__block__, em, multiple}
|
||||
end
|
||||
|
||||
{ast, ctx}
|
||||
@@ -767,7 +775,7 @@ defmodule CljElixir.Transformer do
|
||||
# 6. if / when / cond / case / do
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_if([test, then_form | else_form], _meta, ctx) do
|
||||
defp transform_if([test, then_form | else_form], meta, ctx) do
|
||||
test_ast = transform(test, ctx)
|
||||
then_ast = transform(then_form, ctx)
|
||||
|
||||
@@ -777,18 +785,18 @@ defmodule CljElixir.Transformer do
|
||||
[] -> nil
|
||||
end
|
||||
|
||||
ast = {:if, [], [test_ast, [do: then_ast, else: else_ast]]}
|
||||
ast = {:if, elixir_meta(meta), [test_ast, [do: then_ast, else: else_ast]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_when([test | body], _meta, ctx) do
|
||||
defp transform_when([test | body], meta, ctx) do
|
||||
test_ast = transform(test, ctx)
|
||||
body_ast = transform_body(body, ctx)
|
||||
ast = {:if, [], [test_ast, [do: body_ast]]}
|
||||
ast = {:if, elixir_meta(meta), [test_ast, [do: body_ast]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_cond(pairs, _meta, ctx) do
|
||||
defp transform_cond(pairs, meta, ctx) do
|
||||
clauses =
|
||||
pairs
|
||||
|> Enum.chunk_every(2)
|
||||
@@ -803,11 +811,11 @@ defmodule CljElixir.Transformer do
|
||||
{:->, [], [[cond_ast], result_ast]}
|
||||
end)
|
||||
|
||||
ast = {:cond, [], [[do: clauses]]}
|
||||
ast = {:cond, elixir_meta(meta), [[do: clauses]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_case([val | clauses], _meta, ctx) do
|
||||
defp transform_case([val | clauses], meta, ctx) do
|
||||
val_ast = transform(val, ctx)
|
||||
pattern_ctx = %{ctx | in_pattern: true}
|
||||
|
||||
@@ -828,11 +836,12 @@ defmodule CljElixir.Transformer do
|
||||
{:->, [], [[pat_ast], body_ast]}
|
||||
end)
|
||||
|
||||
ast = {:case, [], [val_ast, [do: case_clauses]]}
|
||||
ast = {:case, elixir_meta(meta), [val_ast, [do: case_clauses]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_do(exprs, _meta, ctx) do
|
||||
# transform_body handles its own block wrapping; meta not needed here
|
||||
body_ast = transform_body(exprs, ctx)
|
||||
{body_ast, ctx}
|
||||
end
|
||||
@@ -841,9 +850,10 @@ defmodule CljElixir.Transformer do
|
||||
# 7. loop / recur
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_loop([{:vector, _, bindings} | body], _meta, ctx) do
|
||||
defp transform_loop([{:vector, _, bindings} | body], meta, ctx) do
|
||||
binding_pairs = Enum.chunk_every(bindings, 2)
|
||||
arity = length(binding_pairs)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
loop_var = unique_var(:loop_fn, ctx)
|
||||
loop_ctx = %{ctx | loop_var: loop_var, loop_arity: arity}
|
||||
@@ -859,16 +869,16 @@ defmodule CljElixir.Transformer do
|
||||
|
||||
# Pattern: loop_fn = fn loop_fn, bindings... -> body end; loop_fn.(loop_fn, init_vals...)
|
||||
fn_params = [loop_var | param_names]
|
||||
fn_ast = {:fn, [], [{:->, [], [fn_params, body_ast]}]}
|
||||
fn_ast = {:fn, em, [{:->, [], [fn_params, body_ast]}]}
|
||||
|
||||
assign = {:=, [], [loop_var, fn_ast]}
|
||||
invoke = {{:., [], [loop_var]}, [], [loop_var | init_vals]}
|
||||
assign = {:=, em, [loop_var, fn_ast]}
|
||||
invoke = {{:., em, [loop_var]}, em, [loop_var | init_vals]}
|
||||
|
||||
ast = {:__block__, [], [assign, invoke]}
|
||||
ast = {:__block__, em, [assign, invoke]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_recur(args, _meta, ctx) do
|
||||
defp transform_recur(args, _meta_unused, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
|
||||
ast =
|
||||
@@ -891,21 +901,22 @@ defmodule CljElixir.Transformer do
|
||||
# 8. def (top-level binding)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_def([name_form, value], _meta, ctx) do
|
||||
defp transform_def([name_form, value], meta, ctx) do
|
||||
fun_name = symbol_to_atom(name_form)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
# Check if value looks like a schema definition
|
||||
case detect_schema(value) do
|
||||
nil ->
|
||||
val_ast = transform(value, ctx)
|
||||
def_ast = {:def, [], [{fun_name, [], []}, [do: val_ast]]}
|
||||
def_ast = {:def, em, [{fun_name, [], []}, [do: val_ast]]}
|
||||
{def_ast, ctx}
|
||||
|
||||
schema_data ->
|
||||
# For schema defs, use the plain data as the runtime value
|
||||
# (avoids trying to transform schema references like PositiveInt as variables)
|
||||
val_ast = Macro.escape(schema_data)
|
||||
def_ast = {:def, [], [{fun_name, [], []}, [do: val_ast]]}
|
||||
def_ast = {:def, em, [{fun_name, [], []}, [do: val_ast]]}
|
||||
|
||||
# Generate type name: "User" -> :user, "PositiveInt" -> :positive_int
|
||||
schema_name = symbol_name(name_form)
|
||||
@@ -931,9 +942,10 @@ defmodule CljElixir.Transformer do
|
||||
end
|
||||
end
|
||||
|
||||
defp transform_def([name_form], _meta, ctx) do
|
||||
defp transform_def([name_form], meta, ctx) do
|
||||
fun_name = symbol_to_atom(name_form)
|
||||
ast = {:def, [], [{fun_name, [], []}, [do: nil]]}
|
||||
em = elixir_meta(meta)
|
||||
ast = {:def, em, [{fun_name, [], []}, [do: nil]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -941,10 +953,11 @@ defmodule CljElixir.Transformer do
|
||||
# 9. Module/function calls (FFI)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_module_call(name, args, ctx) do
|
||||
defp transform_module_call(name, args, meta, ctx) do
|
||||
{mod_ast, fun_atom} = parse_module_function(name)
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
ast = {{:., [], [mod_ast, fun_atom]}, [], t_args}
|
||||
em = elixir_meta(meta)
|
||||
ast = {{:., em, [mod_ast, fun_atom]}, em, t_args}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -982,11 +995,11 @@ defmodule CljElixir.Transformer do
|
||||
# 10. Unqualified function calls
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_unqualified_call(name, args, ctx) do
|
||||
defp transform_unqualified_call(name, args, meta, ctx) do
|
||||
munged = munge_name(name)
|
||||
fun_atom = String.to_atom(munged)
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
ast = {fun_atom, [], t_args}
|
||||
ast = {fun_atom, elixir_meta(meta), t_args}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -1192,7 +1205,7 @@ defmodule CljElixir.Transformer do
|
||||
# 12. Keyword-as-function
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_keyword_call(kw, args, ctx) do
|
||||
defp transform_keyword_call(kw, args, _meta, ctx) do
|
||||
case args do
|
||||
[map_form] ->
|
||||
map_ast = transform(map_form, ctx)
|
||||
@@ -1215,7 +1228,7 @@ defmodule CljElixir.Transformer do
|
||||
# 13. defprotocol
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_defprotocol(args, _meta, ctx) do
|
||||
defp transform_defprotocol(args, meta, ctx) do
|
||||
{name_form, rest} = extract_name(args)
|
||||
proto_alias = module_name_ast(name_form)
|
||||
|
||||
@@ -1265,7 +1278,7 @@ defmodule CljElixir.Transformer do
|
||||
multiple -> {:__block__, [], multiple}
|
||||
end
|
||||
|
||||
ast = {:defprotocol, [context: Elixir], [proto_alias, [do: block]]}
|
||||
ast = {:defprotocol, [context: Elixir] ++ elixir_meta(meta), [proto_alias, [do: block]]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -1273,7 +1286,7 @@ defmodule CljElixir.Transformer do
|
||||
# 14. defrecord
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_defrecord(args, _meta, ctx) do
|
||||
defp transform_defrecord(args, meta, ctx) do
|
||||
{name_form, rest} = extract_name(args)
|
||||
record_alias = module_name_ast(name_form)
|
||||
record_name = symbol_name(name_form)
|
||||
@@ -1348,7 +1361,7 @@ defmodule CljElixir.Transformer do
|
||||
multiple -> {:__block__, [], multiple}
|
||||
end
|
||||
|
||||
ast = {:defmodule, [context: Elixir], [record_alias, [do: block]]}
|
||||
ast = {:defmodule, [context: Elixir] ++ elixir_meta(meta), [record_alias, [do: block]]}
|
||||
{ast, new_ctx}
|
||||
end
|
||||
|
||||
@@ -1374,7 +1387,7 @@ defmodule CljElixir.Transformer do
|
||||
# 15. extend-type / extend-protocol
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_extend_type([type_form | rest], _meta, ctx) do
|
||||
defp transform_extend_type([type_form | rest], _meta_unused, ctx) do
|
||||
type_alias = resolve_type_name(type_form)
|
||||
|
||||
groups = parse_protocol_groups(rest)
|
||||
@@ -1402,7 +1415,7 @@ defmodule CljElixir.Transformer do
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_extend_protocol([proto_form | rest], _meta, ctx) do
|
||||
defp transform_extend_protocol([proto_form | rest], _meta_unused, ctx) do
|
||||
proto_alias = resolve_type_name(proto_form)
|
||||
|
||||
# Parse: TypeName (fn ...) TypeName (fn ...)
|
||||
@@ -1435,7 +1448,7 @@ defmodule CljElixir.Transformer do
|
||||
# 16. reify
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_reify(args, _meta, ctx) do
|
||||
defp transform_reify(args, _meta_unused, ctx) do
|
||||
{counter, new_ctx} = bump_gensym(ctx)
|
||||
mod_name = String.to_atom("CljElixir.Reify_#{counter}")
|
||||
mod_alias = {:__aliases__, [alias: false], [mod_name]}
|
||||
@@ -1475,7 +1488,7 @@ defmodule CljElixir.Transformer do
|
||||
# 17. with
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_with([{:vector, _, bindings} | body_and_else], _meta, ctx) do
|
||||
defp transform_with([{:vector, _, bindings} | body_and_else], meta, ctx) do
|
||||
binding_pairs = Enum.chunk_every(bindings, 2)
|
||||
pattern_ctx = %{ctx | in_pattern: true}
|
||||
|
||||
@@ -1507,7 +1520,7 @@ defmodule CljElixir.Transformer do
|
||||
[do: body_ast, else: else_pairs]
|
||||
end
|
||||
|
||||
ast = {:with, [], with_clauses ++ [opts]}
|
||||
ast = {:with, elixir_meta(meta), with_clauses ++ [opts]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -1522,7 +1535,7 @@ defmodule CljElixir.Transformer do
|
||||
# 18. receive
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_receive(clauses, _meta, ctx) do
|
||||
defp transform_receive(clauses, meta, ctx) do
|
||||
pattern_ctx = %{ctx | in_pattern: true}
|
||||
|
||||
{case_clauses, after_clause} = parse_receive_clauses(clauses)
|
||||
@@ -1556,7 +1569,7 @@ defmodule CljElixir.Transformer do
|
||||
opts ++ [after: [{:->, [], [[timeout_ast], body_ast]}]]
|
||||
end
|
||||
|
||||
ast = {:receive, [], [opts]}
|
||||
ast = {:receive, elixir_meta(meta), [opts]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -1628,21 +1641,21 @@ defmodule CljElixir.Transformer do
|
||||
# 19. for / doseq
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_for([{:vector, _, bindings} | body], _meta, ctx) do
|
||||
defp transform_for([{:vector, _, bindings} | body], meta, ctx) do
|
||||
{generators, filters} = parse_comprehension_bindings(bindings, ctx)
|
||||
body_ast = transform_body(body, ctx)
|
||||
|
||||
args = generators ++ filters ++ [[do: body_ast]]
|
||||
ast = {:for, [], args}
|
||||
ast = {:for, elixir_meta(meta), args}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
defp transform_doseq([{:vector, _, bindings} | body], _meta, ctx) do
|
||||
defp transform_doseq([{:vector, _, bindings} | body], meta, ctx) do
|
||||
{generators, filters} = parse_comprehension_bindings(bindings, ctx)
|
||||
body_ast = transform_body(body, ctx)
|
||||
|
||||
args = generators ++ filters ++ [[do: body_ast]]
|
||||
ast = {:for, [], args}
|
||||
ast = {:for, elixir_meta(meta), args}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -1991,6 +2004,111 @@ defmodule CljElixir.Transformer do
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# (pr-str val) -> CljElixir.Printer.pr_str(val)
|
||||
# (pr-str a b c) -> join pr_str of each with space
|
||||
defp transform_pr_str(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
printer_mod = {:__aliases__, [alias: false], [:CljElixir, :Printer]}
|
||||
|
||||
case t_args do
|
||||
[single] ->
|
||||
ast = {{:., [], [printer_mod, :pr_str]}, [], [single]}
|
||||
{ast, ctx}
|
||||
|
||||
multiple ->
|
||||
strs =
|
||||
Enum.map(multiple, fn a ->
|
||||
{{:., [], [printer_mod, :pr_str]}, [], [a]}
|
||||
end)
|
||||
|
||||
joined =
|
||||
Enum.reduce(tl(strs), hd(strs), fn s, acc ->
|
||||
{:<>, [], [acc, {:<>, [], [" ", s]}]}
|
||||
end)
|
||||
|
||||
{joined, ctx}
|
||||
end
|
||||
end
|
||||
|
||||
# (pr val) -> IO.write(CljElixir.Printer.pr_str(val))
|
||||
# Multiple args separated by spaces
|
||||
defp transform_pr(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
printer_mod = {:__aliases__, [alias: false], [:CljElixir, :Printer]}
|
||||
io_mod = {:__aliases__, [alias: false], [:IO]}
|
||||
|
||||
writes =
|
||||
t_args
|
||||
|> Enum.with_index()
|
||||
|> Enum.flat_map(fn {a, i} ->
|
||||
pr_call = {{:., [], [printer_mod, :pr_str]}, [], [a]}
|
||||
write_call = {{:., [], [io_mod, :write]}, [], [pr_call]}
|
||||
|
||||
if i > 0 do
|
||||
space_call = {{:., [], [io_mod, :write]}, [], [" "]}
|
||||
[space_call, write_call]
|
||||
else
|
||||
[write_call]
|
||||
end
|
||||
end)
|
||||
|
||||
ast = {:__block__, [], writes}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# (prn val) -> IO.puts(CljElixir.Printer.pr_str(val))
|
||||
# Multiple args joined with spaces, then newline
|
||||
defp transform_prn(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
printer_mod = {:__aliases__, [alias: false], [:CljElixir, :Printer]}
|
||||
io_mod = {:__aliases__, [alias: false], [:IO]}
|
||||
|
||||
strs =
|
||||
Enum.map(t_args, fn a ->
|
||||
{{:., [], [printer_mod, :pr_str]}, [], [a]}
|
||||
end)
|
||||
|
||||
joined =
|
||||
case strs do
|
||||
[single] ->
|
||||
single
|
||||
|
||||
multiple ->
|
||||
Enum.reduce(tl(multiple), hd(multiple), fn s, acc ->
|
||||
{:<>, [], [acc, {:<>, [], [" ", s]}]}
|
||||
end)
|
||||
end
|
||||
|
||||
ast = {{:., [], [io_mod, :puts]}, [], [joined]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# (print-str val) -> CljElixir.Printer.print_str(val)
|
||||
# (print-str a b c) -> join print_str of each with space
|
||||
defp transform_print_str(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
printer_mod = {:__aliases__, [alias: false], [:CljElixir, :Printer]}
|
||||
|
||||
case t_args do
|
||||
[single] ->
|
||||
ast = {{:., [], [printer_mod, :print_str]}, [], [single]}
|
||||
{ast, ctx}
|
||||
|
||||
multiple ->
|
||||
strs =
|
||||
Enum.map(multiple, fn a ->
|
||||
{{:., [], [printer_mod, :print_str]}, [], [a]}
|
||||
end)
|
||||
|
||||
joined =
|
||||
Enum.reduce(tl(strs), hd(strs), fn s, acc ->
|
||||
{:<>, [], [acc, {:<>, [], [" ", s]}]}
|
||||
end)
|
||||
|
||||
{joined, ctx}
|
||||
end
|
||||
end
|
||||
|
||||
# nil?
|
||||
defp transform_nil_check([a], ctx) do
|
||||
a_ast = transform(a, ctx)
|
||||
@@ -2034,25 +2152,27 @@ defmodule CljElixir.Transformer do
|
||||
# Positional constructor: (->Name arg1 arg2 ...)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_positional_constructor(name, args, ctx) do
|
||||
defp transform_positional_constructor(name, args, meta, ctx) do
|
||||
# "->Name" → strip ->
|
||||
record_name = String.slice(name, 2..-1//1)
|
||||
mod_alias = parse_module_name(record_name)
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
# Call Module.new(args...)
|
||||
ast = {{:., [], [mod_alias, :new]}, [], t_args}
|
||||
ast = {{:., em, [mod_alias, :new]}, em, t_args}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# Map constructor: (map->Name {:field val ...})
|
||||
defp transform_map_constructor(name, args, ctx) do
|
||||
defp transform_map_constructor(name, args, meta, ctx) do
|
||||
record_name = String.slice(name, 5..-1//1)
|
||||
mod_alias = parse_module_name(record_name)
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
em = elixir_meta(meta)
|
||||
|
||||
# Use Kernel.struct!/2
|
||||
ast = {{:., [], [{:__aliases__, [alias: false], [:Kernel]}, :struct!]}, [], [mod_alias | t_args]}
|
||||
ast = {{:., em, [{:__aliases__, [alias: false], [:Kernel]}, :struct!]}, em, [mod_alias | t_args]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -2822,7 +2942,7 @@ defmodule CljElixir.Transformer do
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# (try body... (catch ...) ... (finally ...))
|
||||
defp transform_try(args, _meta, ctx) do
|
||||
defp transform_try(args, meta, ctx) do
|
||||
{body_forms, catch_clauses, finally_clause} = partition_try_args(args)
|
||||
|
||||
# Transform body
|
||||
@@ -2858,7 +2978,7 @@ defmodule CljElixir.Transformer do
|
||||
try_opts ++ [after: after_ast]
|
||||
end
|
||||
|
||||
ast = {:try, [], [try_opts]}
|
||||
ast = {:try, elixir_meta(meta), [try_opts]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -3274,4 +3394,17 @@ defmodule CljElixir.Transformer do
|
||||
|> String.replace("-", "_")
|
||||
|> String.to_atom()
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source-mapping: CljElixir meta → Elixir AST metadata
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Convert CljElixir meta map to Elixir AST keyword list metadata.
|
||||
# Line must be >= 1 for the Erlang compiler annotation layer;
|
||||
# a zero line/col is treated as absent.
|
||||
defp elixir_meta(%{line: line, col: col}) when line > 0 and col > 0,
|
||||
do: [line: line, column: col]
|
||||
defp elixir_meta(%{line: line}) when line > 0,
|
||||
do: [line: line]
|
||||
defp elixir_meta(_), do: []
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user