defmodule CljElixir.Phase8Test do use ExUnit.Case, async: false defp eval!(source) do case CljElixir.Compiler.eval_string(source) do {:ok, result, _} -> result {:error, errors} -> raise "Compilation failed: #{inspect(errors)}" end end describe "CljElixir.Printer.pr_str" do test "nil" do assert CljElixir.Printer.pr_str(nil) == "nil" end test "booleans" do assert CljElixir.Printer.pr_str(true) == "true" assert CljElixir.Printer.pr_str(false) == "false" end test "integers" do assert CljElixir.Printer.pr_str(42) == "42" assert CljElixir.Printer.pr_str(-7) == "-7" end test "floats" do assert CljElixir.Printer.pr_str(3.14) == "3.14" end test "strings are quoted" do assert CljElixir.Printer.pr_str("hello") == "\"hello\"" end test "strings with escapes" do assert CljElixir.Printer.pr_str("hello\nworld") == "\"hello\\nworld\"" assert CljElixir.Printer.pr_str("say \"hi\"") == "\"say \\\"hi\\\"\"" end test "atoms/keywords" do assert CljElixir.Printer.pr_str(:hello) == ":hello" assert CljElixir.Printer.pr_str(:ok) == ":ok" end test "lists" do assert CljElixir.Printer.pr_str([1, 2, 3]) == "(1 2 3)" assert CljElixir.Printer.pr_str([]) == "()" end test "maps" do result = CljElixir.Printer.pr_str(%{name: "Ada"}) assert result =~ ":name" assert result =~ "\"Ada\"" assert String.starts_with?(result, "{") assert String.ends_with?(result, "}") end test "tuples" do assert CljElixir.Printer.pr_str({:ok, 42}) == "#el[:ok 42]" end test "nested structures" do result = CljElixir.Printer.pr_str(%{data: [1, 2, {:ok, "hi"}]}) assert result =~ ":data" assert result =~ "(1 2 #el[:ok \"hi\"])" end test "MapSet" do result = CljElixir.Printer.pr_str(MapSet.new([:a, :b])) assert String.starts_with?(result, "\#{") assert String.ends_with?(result, "}") end test "module names" do assert CljElixir.Printer.pr_str(Enum) == "Enum" assert CljElixir.Printer.pr_str(IO) == "IO" end test "pids" do result = CljElixir.Printer.pr_str(self()) assert String.starts_with?(result, "#PID<") end end describe "CljElixir.Printer.print_str" do test "strings not quoted" do assert CljElixir.Printer.print_str("hello") == "hello" end test "non-strings same as pr_str" do assert CljElixir.Printer.print_str(42) == "42" assert CljElixir.Printer.print_str(:ok) == ":ok" end end describe "pr-str builtin" do test "pr-str on integer" do result = eval!("(pr-str 42)") assert result == "42" end test "pr-str on string" do result = eval!("(pr-str \"hello\")") assert result == "\"hello\"" end test "pr-str on keyword" do result = eval!("(pr-str :foo)") assert result == ":foo" end test "pr-str on list" do result = eval!("(pr-str (list 1 2 3))") assert result == "(1 2 3)" end test "pr-str on map" do result = eval!("(pr-str {:a 1})") assert result =~ ":a" assert result =~ "1" end test "pr-str on tuple" do result = eval!("(pr-str #el[:ok 42])") assert result == "#el[:ok 42]" end test "pr-str on nil" do result = eval!("(pr-str nil)") assert result == "nil" end test "pr-str on boolean" do result = eval!("(pr-str true)") assert result == "true" end test "pr-str multiple args joined with space" do result = eval!("(pr-str 1 2 3)") assert result == "1 2 3" end end describe "print-str builtin" do test "print-str on string (no quotes)" do result = eval!("(print-str \"hello\")") assert result == "hello" end test "print-str on integer" do result = eval!("(print-str 42)") assert result == "42" end test "print-str multiple args joined with space" do result = eval!("(print-str \"hello\" \"world\")") assert result == "hello world" end end describe "prn builtin" do test "prn outputs to stdout with newline" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(prn 42)") end) assert String.trim(output) == "42" end test "prn with string (quoted)" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(prn \"hello\")") end) assert String.trim(output) == "\"hello\"" end test "prn multiple args" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(prn 1 2 3)") end) assert String.trim(output) == "1 2 3" end end describe "pr builtin" do test "pr outputs to stdout without newline" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(pr 42)") end) assert output == "42" end test "pr with string (quoted)" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(pr \"hello\")") end) assert output == "\"hello\"" end test "pr multiple args separated by spaces" do output = ExUnit.CaptureIO.capture_io(fn -> eval!("(pr 1 2 3)") end) assert output == "1 2 3" end end # --------------------------------------------------------------------------- # Source-mapped line/col metadata # --------------------------------------------------------------------------- describe "source-mapped metadata" do test "symbol AST carries line/col from reader" do {:ok, ast} = CljElixir.Compiler.compile_string("(+ x 1)") # x should have line: 1 metadata in the Elixir AST # Walk the AST to find the :x variable {_, found} = Macro.prewalk(ast, false, fn {:x, meta, nil} = node, _acc when is_list(meta) -> {node, meta[:line] == 1} node, acc -> {node, acc} end) assert found, "variable :x should have line: 1 metadata" end test "defmodule AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(defmodule Foo (defn bar [] 1))") # The defmodule node should have line: 1 {:defmodule, meta, _} = ast assert meta[:line] == 1 end test "defn AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string(""" (defmodule MetaTest1 (defn foo [x] x)) """) # Find the :def node inside the module body {_, found_line} = Macro.prewalk(ast, nil, fn {:def, meta, _} = node, nil when is_list(meta) -> {node, Keyword.get(meta, :line)} node, acc -> {node, acc} end) assert found_line == 2, "defn should have line: 2 metadata" end test "if AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(if true 1 2)") {:if, meta, _} = ast assert meta[:line] == 1 end test "fn AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(fn [x] x)") {:fn, meta, _} = ast assert meta[:line] == 1 end test "case AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(case 1 1 :one 2 :two)") {:case, meta, _} = ast assert meta[:line] == 1 end test "cond AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(cond true :yes)") {:cond, meta, _} = ast assert meta[:line] == 1 end test "module call AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(Enum/map [1 2] inc)") # The outer call should be {{:., meta, [Enum, :map]}, meta, args} {{:., dot_meta, _}, call_meta, _} = ast assert dot_meta[:line] == 1 assert call_meta[:line] == 1 end test "unqualified call AST carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string(""" (defmodule MetaTest2 (defn foo [] (bar 1))) """) # Find the :bar call inside the module {_, found_line} = Macro.prewalk(ast, nil, fn {:bar, meta, [1]} = node, nil when is_list(meta) -> {node, meta[:line]} node, acc -> {node, acc} end) assert found_line == 2, "unqualified call should have line: 2 metadata" end test "runtime error has source location" do # This tests that evaluated code preserves source info result = CljElixir.Compiler.eval_string(""" (defmodule LineTest1 (defn hello [name] (str "hello " name))) (LineTest1/hello "world") """, file: "line_test.clje") assert {:ok, "hello world", _} = result end test "let block carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(let [x 1] x)") # let produces either a block or a single = expression case ast do {:__block__, meta, _} -> assert meta[:line] == 1 {:=, meta, _} -> assert meta[:line] == 1 end end test "for comprehension carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string("(for [x [1 2 3]] x)") {:for, meta, _} = ast assert meta[:line] == 1 end test "try carries line metadata" do {:ok, ast} = CljElixir.Compiler.compile_string(""" (try (throw "oops") (catch e (str "caught: " e))) """) {:try, meta, _} = ast assert meta[:line] == 1 end end end