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:
2026-03-08 11:03:10 -04:00
parent d8719b6d48
commit 7e82efd7ec
14 changed files with 1946 additions and 73 deletions
+202 -69
View File
@@ -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