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