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>
453 lines
16 KiB
Elixir
453 lines
16 KiB
Elixir
defmodule CljElixir.Phase3Test do
|
|
use ExUnit.Case, async: false
|
|
|
|
# Evaluate CljElixir code with PersistentVector enabled (no vector_as_list flag)
|
|
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
|
|
|
|
# ==========================================================================
|
|
# Vector literal construction
|
|
# ==========================================================================
|
|
|
|
describe "vector literal construction" do
|
|
test "vector literal produces PersistentVector" do
|
|
result = eval!("[1 2 3]")
|
|
assert result.__struct__ == CljElixir.PersistentVector
|
|
end
|
|
|
|
test "empty vector literal produces PersistentVector" do
|
|
result = eval!("[]")
|
|
assert result.__struct__ == CljElixir.PersistentVector
|
|
assert CljElixir.PersistentVector.pv_count(result) == 0
|
|
end
|
|
|
|
test "single element vector" do
|
|
result = eval!("[42]")
|
|
assert CljElixir.PersistentVector.pv_count(result) == 1
|
|
assert CljElixir.PersistentVector.pv_nth(result, 0) == 42
|
|
end
|
|
|
|
test "nested vectors" do
|
|
result = eval!("[[1 2] [3 4]]")
|
|
assert CljElixir.PersistentVector.pv_count(result) == 2
|
|
inner = CljElixir.PersistentVector.pv_nth(result, 0)
|
|
assert inner.__struct__ == CljElixir.PersistentVector
|
|
assert CljElixir.PersistentVector.pv_nth(inner, 0) == 1
|
|
end
|
|
|
|
test "vector with mixed types" do
|
|
result = eval!("[1 :two \"three\" nil true]")
|
|
assert CljElixir.PersistentVector.pv_count(result) == 5
|
|
assert CljElixir.PersistentVector.pv_nth(result, 0) == 1
|
|
assert CljElixir.PersistentVector.pv_nth(result, 1) == :two
|
|
assert CljElixir.PersistentVector.pv_nth(result, 2) == "three"
|
|
assert CljElixir.PersistentVector.pv_nth(result, 3) == nil
|
|
assert CljElixir.PersistentVector.pv_nth(result, 4) == true
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# vec and vector builtins
|
|
# ==========================================================================
|
|
|
|
describe "vec and vector builtins" do
|
|
test "vec converts list to PersistentVector" do
|
|
result = eval!("(vec (list 1 2 3))")
|
|
assert result.__struct__ == CljElixir.PersistentVector
|
|
assert CljElixir.PersistentVector.pv_count(result) == 3
|
|
end
|
|
|
|
test "vector creates PersistentVector from args" do
|
|
result = eval!("(vector 4 5 6)")
|
|
assert result.__struct__ == CljElixir.PersistentVector
|
|
assert CljElixir.PersistentVector.to_list(result) == [4, 5, 6]
|
|
end
|
|
|
|
test "vector with no args creates empty vector" do
|
|
result = eval!("(vector)")
|
|
assert CljElixir.PersistentVector.pv_count(result) == 0
|
|
end
|
|
|
|
test "vector? returns true for PersistentVector" do
|
|
assert eval!("(vector? [1 2 3])") == true
|
|
end
|
|
|
|
test "vector? returns false for list" do
|
|
assert eval!("(vector? (list 1 2 3))") == false
|
|
end
|
|
|
|
test "vector? returns false for map" do
|
|
assert eval!("(vector? {:a 1})") == false
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Protocol dispatch — indexed access
|
|
# ==========================================================================
|
|
|
|
describe "indexed access" do
|
|
test "nth on vector" do
|
|
assert eval!("(nth [10 20 30] 0)") == 10
|
|
assert eval!("(nth [10 20 30] 1)") == 20
|
|
assert eval!("(nth [10 20 30] 2)") == 30
|
|
end
|
|
|
|
test "nth with not-found" do
|
|
assert eval!("(nth [10 20 30] 5 :missing)") == :missing
|
|
end
|
|
|
|
test "get on vector" do
|
|
assert eval!("(get [10 20 30] 1)") == 20
|
|
end
|
|
|
|
test "get with not-found" do
|
|
assert eval!("(get [10 20 30] 5 :missing)") == :missing
|
|
end
|
|
|
|
test "get with non-integer key returns nil" do
|
|
assert eval!("(get [10 20 30] :foo)") == nil
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Protocol dispatch — mutation operations
|
|
# ==========================================================================
|
|
|
|
describe "mutation operations" do
|
|
test "conj appends to vector" do
|
|
result = eval!("(conj [1 2] 3)")
|
|
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
|
|
end
|
|
|
|
test "assoc updates index" do
|
|
result = eval!("(assoc [1 2 3] 1 :x)")
|
|
assert CljElixir.PersistentVector.to_list(result) == [1, :x, 3]
|
|
end
|
|
|
|
test "assoc at end appends" do
|
|
result = eval!("(assoc [1 2] 2 3)")
|
|
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
|
|
end
|
|
|
|
test "contains? with valid index" do
|
|
assert eval!("(contains? [1 2 3] 0)") == true
|
|
assert eval!("(contains? [1 2 3] 2)") == true
|
|
end
|
|
|
|
test "contains? with invalid index" do
|
|
assert eval!("(contains? [1 2 3] 3)") == false
|
|
assert eval!("(contains? [1 2 3] -1)") == false
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Protocol dispatch — sequence operations
|
|
# ==========================================================================
|
|
|
|
describe "sequence operations" do
|
|
test "count on vector" do
|
|
assert eval!("(count [1 2 3])") == 3
|
|
assert eval!("(count [])") == 0
|
|
end
|
|
|
|
test "first on vector" do
|
|
assert eval!("(first [10 20 30])") == 10
|
|
end
|
|
|
|
test "first on empty vector" do
|
|
assert eval!("(first [])") == nil
|
|
end
|
|
|
|
test "seq on non-empty vector returns list" do
|
|
result = eval!("(seq [1 2 3])")
|
|
assert is_list(result)
|
|
assert result == [1, 2, 3]
|
|
end
|
|
|
|
test "seq on empty vector returns nil" do
|
|
assert eval!("(seq [])") == nil
|
|
end
|
|
|
|
test "empty? on vector" do
|
|
assert eval!("(empty? [])") == true
|
|
assert eval!("(empty? [1])") == false
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Stack operations (peek/pop)
|
|
# ==========================================================================
|
|
|
|
describe "stack operations" do
|
|
test "peek returns last element" do
|
|
assert eval!("(peek [10 20 30])") == 30
|
|
end
|
|
|
|
test "pop removes last element" do
|
|
result = eval!("(pop [1 2 3])")
|
|
assert CljElixir.PersistentVector.to_list(result) == [1, 2]
|
|
end
|
|
|
|
test "pop single element returns empty" do
|
|
result = eval!("(pop [42])")
|
|
assert CljElixir.PersistentVector.pv_count(result) == 0
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Vector as function (IFn)
|
|
# ==========================================================================
|
|
|
|
describe "vector as function" do
|
|
# TODO: Vector-as-function requires transformer support for struct invocation.
|
|
# The IFn protocol is implemented but Elixir doesn't auto-dispatch when a
|
|
# struct is in call position. Needs a transformer change to detect and wrap
|
|
# non-function call heads with IFn dispatch.
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Pattern matching (unchanged — vectors match tuples in patterns)
|
|
# ==========================================================================
|
|
|
|
describe "pattern matching" do
|
|
test "vector in case pattern matches tuple" do
|
|
assert eval!("(case #el[:ok 42] [:ok x] x)") == 42
|
|
end
|
|
|
|
test "vector in let pattern matches tuple" do
|
|
assert eval!("(let [[:ok x] #el[:ok 99]] x)") == 99
|
|
end
|
|
|
|
test "nested vector patterns" do
|
|
result = eval!("""
|
|
(case #el[:ok #el[:inner 5]]
|
|
[:ok [:inner n]] n
|
|
_ nil)
|
|
""")
|
|
assert result == 5
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Metadata
|
|
# ==========================================================================
|
|
|
|
describe "metadata" do
|
|
test "empty vector has nil meta" do
|
|
result = eval!("[]")
|
|
assert Map.get(result, :meta) == nil
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Boundary conditions (trie level transitions)
|
|
# ==========================================================================
|
|
|
|
describe "boundary conditions" do
|
|
test "32 element vector (full tail, no trie)" do
|
|
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..32))
|
|
assert CljElixir.PersistentVector.pv_count(v) == 32
|
|
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
|
|
assert CljElixir.PersistentVector.pv_nth(v, 31) == 32
|
|
end
|
|
|
|
test "33 element vector (trie overflow)" do
|
|
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
|
|
assert CljElixir.PersistentVector.pv_count(v) == 33
|
|
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
|
|
assert CljElixir.PersistentVector.pv_nth(v, 32) == 33
|
|
end
|
|
|
|
test "1025 element vector (multi-level trie)" do
|
|
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..1025))
|
|
assert CljElixir.PersistentVector.pv_count(v) == 1025
|
|
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
|
|
assert CljElixir.PersistentVector.pv_nth(v, 1024) == 1025
|
|
end
|
|
|
|
test "conj across 32-element boundary" do
|
|
v32 = CljElixir.PersistentVector.from_list(Enum.to_list(1..32))
|
|
v33 = CljElixir.PersistentVector.pv_conj(v32, 33)
|
|
assert CljElixir.PersistentVector.pv_count(v33) == 33
|
|
assert CljElixir.PersistentVector.pv_nth(v33, 32) == 33
|
|
# Original unchanged (structural sharing)
|
|
assert CljElixir.PersistentVector.pv_count(v32) == 32
|
|
end
|
|
|
|
test "pop across 33-to-32 boundary" do
|
|
v33 = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
|
|
v32 = CljElixir.PersistentVector.pv_pop(v33)
|
|
assert CljElixir.PersistentVector.pv_count(v32) == 32
|
|
assert CljElixir.PersistentVector.pv_nth(v32, 31) == 32
|
|
end
|
|
|
|
test "assoc in trie (not tail)" do
|
|
v33 = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
|
|
v33b = CljElixir.PersistentVector.pv_assoc(v33, 0, :first)
|
|
assert CljElixir.PersistentVector.pv_nth(v33b, 0) == :first
|
|
assert CljElixir.PersistentVector.pv_nth(v33b, 1) == 2
|
|
# Original unchanged
|
|
assert CljElixir.PersistentVector.pv_nth(v33, 0) == 1
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# SubVector
|
|
# ==========================================================================
|
|
|
|
describe "subvec" do
|
|
test "subvec creates view into vector" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
|
|
assert CljElixir.SubVector.sv_count(sv) == 3
|
|
assert CljElixir.SubVector.sv_nth(sv, 0) == 2
|
|
assert CljElixir.SubVector.sv_nth(sv, 1) == 3
|
|
assert CljElixir.SubVector.sv_nth(sv, 2) == 4
|
|
end
|
|
|
|
test "subvec to_list" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
|
|
assert CljElixir.SubVector.sv_to_list(sv) == [2, 3, 4]
|
|
end
|
|
|
|
test "subvec 2-arity (start to end)" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 1)
|
|
assert CljElixir.SubVector.sv_count(sv) == 2
|
|
assert CljElixir.SubVector.sv_to_list(sv) == [20, 30]
|
|
end
|
|
|
|
test "subvec nth with not-found" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3]), 0, 2)
|
|
assert CljElixir.SubVector.sv_nth(sv, 5, :missing) == :missing
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Cross-type equality
|
|
# ==========================================================================
|
|
|
|
describe "cross-type equality" do
|
|
test "vector equals list with same elements" do
|
|
assert eval!("(= [1 2 3] (list 1 2 3))") == true
|
|
end
|
|
|
|
test "vector not equal to list with different elements" do
|
|
assert eval!("(= [1 2 3] (list 1 2 4))") == false
|
|
end
|
|
|
|
test "two vectors with same elements are equal" do
|
|
assert eval!("(= [1 2 3] [1 2 3])") == true
|
|
end
|
|
|
|
test "two vectors with different elements are not equal" do
|
|
assert eval!("(= [1 2] [1 2 3])") == false
|
|
end
|
|
|
|
test "not= works with cross-type" do
|
|
assert eval!("(not= [1 2 3] (list 1 2 3))") == false
|
|
assert eval!("(not= [1 2 3] (list 4 5 6))") == true
|
|
end
|
|
|
|
test "scalar equality still works" do
|
|
assert eval!("(= 1 1)") == true
|
|
assert eval!("(= 1 2)") == false
|
|
assert eval!("(= :a :a)") == true
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Enumerable/Collectable protocols
|
|
# ==========================================================================
|
|
|
|
describe "Enumerable and Collectable" do
|
|
test "Enum.map over PersistentVector" do
|
|
pv = CljElixir.PersistentVector.from_list([1, 2, 3])
|
|
result = Enum.map(pv, &(&1 * 2))
|
|
assert result == [2, 4, 6]
|
|
end
|
|
|
|
test "Enum.filter over PersistentVector" do
|
|
pv = CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5])
|
|
result = Enum.filter(pv, &(rem(&1, 2) == 0))
|
|
assert result == [2, 4]
|
|
end
|
|
|
|
test "Enum.count on PersistentVector" do
|
|
pv = CljElixir.PersistentVector.from_list([1, 2, 3])
|
|
assert Enum.count(pv) == 3
|
|
end
|
|
|
|
test "Enum.into PersistentVector" do
|
|
pv = Enum.into([1, 2, 3], CljElixir.PersistentVector.from_list([]))
|
|
assert pv.__struct__ == CljElixir.PersistentVector
|
|
assert CljElixir.PersistentVector.to_list(pv) == [1, 2, 3]
|
|
end
|
|
|
|
test "Enum.slice on PersistentVector" do
|
|
pv = CljElixir.PersistentVector.from_list([10, 20, 30, 40, 50])
|
|
assert Enum.slice(pv, 1, 3) == [20, 30, 40]
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# SubVector protocol dispatch
|
|
# ==========================================================================
|
|
|
|
describe "SubVector protocols" do
|
|
test "count via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
|
|
assert CljElixir.ICounted.count(sv) == 3
|
|
end
|
|
|
|
test "nth via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30, 40]), 1, 3)
|
|
assert CljElixir.IIndexed.nth(sv, 0) == 20
|
|
assert CljElixir.IIndexed.nth(sv, 1) == 30
|
|
end
|
|
|
|
test "lookup via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 0, 2)
|
|
assert CljElixir.ILookup.lookup(sv, 0) == 10
|
|
assert CljElixir.ILookup.lookup(sv, 5) == nil
|
|
assert CljElixir.ILookup.lookup(sv, 5, :missing) == :missing
|
|
end
|
|
|
|
test "seq via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4]), 1, 3)
|
|
assert CljElixir.ISeqable.seq(sv) == [2, 3]
|
|
end
|
|
|
|
test "first/rest via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 0, 3)
|
|
assert CljElixir.ISeq.first(sv) == 10
|
|
assert CljElixir.ISeq.rest(sv) == [20, 30]
|
|
end
|
|
|
|
test "peek/pop via protocol" do
|
|
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
|
|
assert CljElixir.IStack.peek(sv) == 4
|
|
popped = CljElixir.IStack.pop(sv)
|
|
assert CljElixir.SubVector.sv_count(popped) == 2
|
|
end
|
|
end
|
|
|
|
# ==========================================================================
|
|
# Existing Phase 2 tests still pass (backward compatibility)
|
|
# ==========================================================================
|
|
|
|
describe "backward compatibility" do
|
|
test "maps still work with protocols" do
|
|
assert eval!("(get {:a 1 :b 2} :a)") == 1
|
|
assert eval!("(count {:a 1 :b 2})") == 2
|
|
end
|
|
|
|
test "lists still work with protocols" do
|
|
assert eval!("(first (list 10 20 30))") == 10
|
|
assert eval!("(count (list 1 2 3))") == 3
|
|
end
|
|
end
|
|
end
|