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>
393 lines
11 KiB
Elixir
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
|