Files
Adam d8719b6d48 Phases 1-7: Complete CljElixir compiler through Malli schema adapter
Bootstrap compiler (reader, analyzer, transformer, compiler, Mix plugin),
core protocols (16 protocols for Map/List/Tuple/BitString), PersistentVector
(bit-partitioned trie), domain tools (clojurify/elixirify), BEAM concurrency
(receive, spawn, GenServer), control flow & macros (threading, try/catch,
destructuring, defmacro with quasiquote/auto-gensym), and Malli schema
adapter (m/=> specs, auto @type, recursive schemas, cross-references).

537 compiler tests + 55 Malli unit tests + 15 integration tests = 607 total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:38:22 -04:00

393 lines
11 KiB
Elixir

defmodule CljElixir.Phase4Test do
use ExUnit.Case, async: false
# Evaluate CljElixir code with PersistentVector enabled
defp eval!(source) do
case CljElixir.Compiler.eval_string(source) do
{:ok, result, _bindings} -> result
{:error, errors} -> raise "CljElixir eval error: #{inspect(errors)}"
end
end
# ==========================================================================
# tuple function
# ==========================================================================
describe "tuple function" do
test "empty tuple" do
result = eval!("(tuple)")
assert result == {}
end
test "single element tuple" do
result = eval!("(tuple :ok)")
assert result == {:ok}
end
test "two element tuple" do
result = eval!("(tuple :ok \"data\")")
assert result == {:ok, "data"}
end
test "three element tuple" do
result = eval!("(tuple 1 2 3)")
assert result == {1, 2, 3}
end
test "tuple with mixed types" do
result = eval!("(tuple :error 404 \"not found\")")
assert result == {:error, 404, "not found"}
end
test "tuple with nested tuple" do
result = eval!("(tuple :ok (tuple 1 2))")
assert result == {:ok, {1, 2}}
end
test "tuple-size on constructed tuple" do
result = eval!("(tuple-size (tuple :a :b :c))")
assert result == 3
end
test "elem on constructed tuple" do
result = eval!("(elem (tuple :a :b :c) 1)")
assert result == :b
end
test "tuple in let binding" do
result = eval!("""
(let [t (tuple :ok 42)]
(elem t 1))
""")
assert result == 42
end
test "tuple with expressions as arguments" do
result = eval!("(tuple (+ 1 2) (* 3 4))")
assert result == {3, 12}
end
end
# ==========================================================================
# clojurify
# ==========================================================================
describe "clojurify" do
test "tuple to vector" do
result = eval!("(clojurify #el[:ok \"data\"])")
assert result.__struct__ == CljElixir.PersistentVector
list = CljElixir.PersistentVector.to_list(result)
assert list == [:ok, "data"]
end
test "list to vector" do
result = eval!("(clojurify '(1 2 3))")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "nested tuple deep conversion" do
result = eval!("(clojurify #el[:ok #el[:nested \"data\"]])")
assert result.__struct__ == CljElixir.PersistentVector
list = CljElixir.PersistentVector.to_list(result)
assert hd(list) == :ok
inner = hd(tl(list))
assert inner.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(inner) == [:nested, "data"]
end
test "map values walked" do
result = eval!("(clojurify {:a #el[1 2]})")
assert is_map(result)
inner = Map.get(result, :a)
assert inner.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(inner) == [1, 2]
end
test "vector idempotent" do
result = eval!("(clojurify [1 2 3])")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "scalar passthrough - integer" do
assert eval!("(clojurify 42)") == 42
end
test "scalar passthrough - string" do
assert eval!("(clojurify \"hello\")") == "hello"
end
test "scalar passthrough - atom" do
assert eval!("(clojurify :foo)") == :foo
end
test "scalar passthrough - nil" do
assert eval!("(clojurify nil)") == nil
end
end
# ==========================================================================
# elixirify
# ==========================================================================
describe "elixirify" do
test "vector to list" do
result = eval!("(elixirify [1 2 3])")
assert is_list(result)
assert result == [1, 2, 3]
end
test "nested vector deep conversion" do
result = eval!("(elixirify [:ok [:nested \"data\"]])")
assert is_list(result)
assert result == [:ok, [:nested, "data"]]
end
test "map values walked" do
result = eval!("(elixirify {:a [1 2]})")
assert is_map(result)
assert Map.get(result, :a) == [1, 2]
end
test "list idempotent" do
result = eval!("(elixirify '(1 2 3))")
assert is_list(result)
assert result == [1, 2, 3]
end
test "tuple elements walked" do
result = eval!("(elixirify #el[:ok [1 2]])")
assert is_tuple(result)
assert elem(result, 0) == :ok
assert is_list(elem(result, 1))
assert elem(result, 1) == [1, 2]
end
test "scalar passthrough - integer" do
assert eval!("(elixirify 42)") == 42
end
test "scalar passthrough - string" do
assert eval!("(elixirify \"hello\")") == "hello"
end
test "scalar passthrough - atom" do
assert eval!("(elixirify :foo)") == :foo
end
end
# ==========================================================================
# Integration: roundtrips and composition
# ==========================================================================
describe "roundtrip conversions" do
test "clojurify then elixirify roundtrip on tuple" do
result = eval!("""
(elixirify (clojurify #el[:ok "data"]))
""")
assert is_list(result)
assert result == [:ok, "data"]
end
test "elixirify then clojurify roundtrip on vector" do
result = eval!("""
(clojurify (elixirify [1 2 3]))
""")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "deep nested roundtrip" do
result = eval!("""
(elixirify (clojurify #el[:ok #el[1 #el[2 3]]]))
""")
assert is_list(result)
assert result == [:ok, [1, [2, 3]]]
end
test "map with nested roundtrip" do
result = eval!("""
(elixirify (clojurify {:a #el[1 2] :b #el[3 4]}))
""")
assert is_map(result)
assert Map.get(result, :a) == [1, 2]
assert Map.get(result, :b) == [3, 4]
end
end
describe "tuple function with clojurify/elixirify" do
test "tuple function result can be clojurified" do
result = eval!("""
(clojurify (tuple :ok "data"))
""")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [:ok, "data"]
end
test "elixirify vector matches tuple construction" do
# elixirify produces a list, not a tuple (by spec)
result = eval!("(elixirify [1 2 3])")
assert is_list(result)
assert result == [1, 2, 3]
end
test "tuple-size on tuple function result" do
result = eval!("(tuple-size (tuple :a :b :c :d))")
assert result == 4
end
end
describe "composition with core functions" do
test "map over list then clojurify" do
result = eval!("""
(clojurify (Enum/map '(1 2 3) (fn [x] (* x 2))))
""")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [2, 4, 6]
end
test "elixirify vector for Enum interop" do
result = eval!("""
(Enum/sum (elixirify [1 2 3 4 5]))
""")
assert result == 15
end
test "clojurify in let binding" do
result = eval!("""
(let [v (clojurify #el[:ok 42])]
(nth v 1))
""")
assert result == 42
end
test "elixirify in let binding" do
result = eval!("""
(let [lst (elixirify [10 20 30])]
(hd lst))
""")
assert result == 10
end
end
# ==========================================================================
# SubVector clojurify/elixirify
# ==========================================================================
describe "SubVector clojurify" do
test "clojurify subvec returns vector" do
result = eval!("""
(let [v [1 2 3 4 5]
sv (subvec v 1 4)]
(clojurify sv))
""")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [2, 3, 4]
end
end
describe "SubVector elixirify" do
test "elixirify subvec returns list" do
result = eval!("""
(let [v [1 2 3 4 5]
sv (subvec v 1 4)]
(elixirify sv))
""")
assert is_list(result)
assert result == [2, 3, 4]
end
end
# ==========================================================================
# Protocol extensibility
# ==========================================================================
describe "protocol extensibility" do
test "defrecord can extend IElixirify" do
result = eval!("""
(defmodule TestUser
(defrecord User [name age]
CljElixir.IElixirify
(-elixirify [u] {:name (Map/get u :name) :age (Map/get u :age) :type "user"})))
(let [u (TestUser.User/new "Alice" 30)]
(elixirify u))
""")
assert is_map(result)
assert Map.get(result, :name) == "Alice"
assert Map.get(result, :age) == 30
assert Map.get(result, :type) == "user"
end
test "defrecord can extend IClojurify" do
result = eval!("""
(defmodule TestPoint
(defrecord Point [x y]
CljElixir.IClojurify
(-clojurify [p] [(Map/get p :x) (Map/get p :y)])))
(let [p (TestPoint.Point/new 10 20)]
(clojurify p))
""")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [10, 20]
end
end
# ==========================================================================
# Tuple sequence and collection operations
# ==========================================================================
describe "tuple sequence operations" do
test "seq on tuple" do
result = eval!("(seq #el[1 2 3])")
assert is_list(result)
assert result == [1, 2, 3]
end
test "seq on empty tuple" do
result = eval!("(seq (tuple))")
assert result == nil
end
test "first on tuple" do
result = eval!("(first #el[:a :b :c])")
assert result == :a
end
test "rest on tuple" do
result = eval!("(rest #el[:a :b :c])")
assert is_list(result)
assert result == [:b, :c]
end
test "conj on tuple" do
result = eval!("(conj #el[1 2] 3)")
assert is_tuple(result)
assert result == {1, 2, 3}
end
test "into empty tuple from vector" do
result = eval!("(into (tuple) [1 2 3])")
assert is_tuple(result)
assert result == {1, 2, 3}
end
test "into vector from tuple" do
result = eval!("(into [] #el[1 2 3])")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "into empty tuple from list" do
result = eval!("(into (tuple) '(1 2 3))")
assert is_tuple(result)
assert result == {1, 2, 3}
end
test "count on tuple via seq" do
result = eval!("(count #el[1 2 3 4])")
assert result == 4
end
end
end