defmodule CljElixir.TransformerTest do use ExUnit.Case, async: true alias CljElixir.Transformer alias CljElixir.Transformer.Context # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- # Parse CljElixir source, transform, return Elixir AST defp transform(source) do {:ok, forms} = CljElixir.Reader.read_string(source) Transformer.transform(forms) end # Parse, transform, eval, return result # Uses vector_as_list: true until PersistentVector is implemented (Phase 3 WS-3) defp eval(source) do {:ok, result, _bindings} = CljElixir.Compiler.eval_string(source, vector_as_list: true) result end # Generate a unique module name string and its Elixir module atom defp unique_mod(prefix) do n = System.unique_integer([:positive]) name_str = "#{prefix}#{n}" mod_atom = String.to_atom("Elixir.#{name_str}") {name_str, mod_atom} end # --------------------------------------------------------------------------- # 1. defmodule # --------------------------------------------------------------------------- describe "defmodule" do test "basic module definition" do {name, mod} = unique_mod("TfTestMod") source = "(defmodule #{name} (defn hello [] :world))" eval(source) assert apply(mod, :hello, []) == :world end test "module with docstring" do ast = transform(~S|(defmodule MyMod "A test module" (def x 1))|) ast_str = Macro.to_string(ast) assert ast_str =~ "moduledoc" end test "module with multiple defs" do {name, mod} = unique_mod("TfTestMultiDef") source = """ (defmodule #{name} (defn add [x y] (+ x y)) (defn sub [x y] (- x y))) """ eval(source) assert apply(mod, :add, [3, 4]) == 7 assert apply(mod, :sub, [10, 3]) == 7 end end # --------------------------------------------------------------------------- # 2. defn / defn- # --------------------------------------------------------------------------- describe "defn" do test "single-arity function" do {name, mod} = unique_mod("TfTestDefn1") source = """ (defmodule #{name} (defn double [x] (* x 2))) """ eval(source) assert apply(mod, :double, [5]) == 10 end test "multi-clause function" do {name, mod} = unique_mod("TfTestDefn2") source = """ (defmodule #{name} (defn greet ([name] (str "hello " name)) ([name greeting] (str greeting " " name)))) """ eval(source) assert apply(mod, :greet, ["Ada"]) == "hello Ada" assert apply(mod, :greet, ["Ada", "hi"]) == "hi Ada" end test "defn with docstring" do ast = transform(~S|(defn hello "Greets someone" [name] (str "hi " name))|) ast_str = Macro.to_string(ast) assert ast_str =~ "doc" end test "defn- produces private function" do ast = transform("(defn- helper [x] (* x 2))") ast_str = Macro.to_string(ast) assert ast_str =~ "defp" end test "defn with multiple body expressions" do {name, mod} = unique_mod("TfTestDefnBody") source = """ (defmodule #{name} (defn process [x] (+ x 1) (* x 2))) """ eval(source) # Last expression is returned assert apply(mod, :process, [5]) == 10 end end # --------------------------------------------------------------------------- # 3. fn (anonymous functions) # --------------------------------------------------------------------------- describe "fn" do test "single-arity anonymous function" do assert eval("((fn [x] (* x x)) 5)") == 25 end test "multi-clause same-arity anonymous function" do # Elixir anonymous functions support multiple clauses of same arity result = eval("((fn ([0] :zero) ([x] x)) 42)") assert result == 42 end test "fn used as argument to HOF" do result = eval("(Enum/map [1 2 3] (fn [x] (* x x)))") assert result == [1, 4, 9] end test "fn with two params" do result = eval("((fn [x y] (+ x y)) 3 4)") assert result == 7 end test "fn with no params" do result = eval("((fn [] 42))") assert result == 42 end end # --------------------------------------------------------------------------- # 4. #() anonymous shorthand # --------------------------------------------------------------------------- describe "anonymous fn shorthand" do test "single arg with %" do result = eval("(Enum/map [1 2 3] #(* % 2))") assert result == [2, 4, 6] end test "two args with %1 %2" do result = eval("(Enum/reduce [1 2 3 4 5] 0 #(+ %1 %2))") assert result == 15 end test "produces correct arity" do ast = transform("#(+ %1 %2)") {:fn, _, [{:->, _, [params, _body]}]} = ast assert length(params) == 2 end end # --------------------------------------------------------------------------- # 5. let # --------------------------------------------------------------------------- describe "let" do test "basic let binding" do assert eval("(let [x 1 y 2] (+ x y))") == 3 end test "let with multiple bindings" do assert eval("(let [a 1 b 2 c 3] (+ a (+ b c)))") == 6 end test "let bindings are sequential" do assert eval("(let [x 1 y (+ x 1)] y)") == 2 end test "let with expression in binding" do assert eval("(let [x (* 3 4)] x)") == 12 end end # --------------------------------------------------------------------------- # 6. if / when / cond / case / do # --------------------------------------------------------------------------- describe "if" do test "if with true condition" do assert eval("(if true :yes :no)") == :yes end test "if with false condition" do assert eval("(if false :yes :no)") == :no end test "if without else returns nil on false" do assert eval("(if false :yes)") == nil end test "if with complex condition" do assert eval("(if (> 5 3) :greater :lesser)") == :greater end end describe "when" do test "when with true condition" do assert eval("(when true :yes)") == :yes end test "when with false condition returns nil" do assert eval("(when false :yes)") == nil end test "when with multiple body expressions" do result = eval("(when true 1 2 3)") assert result == 3 end end describe "cond" do test "basic cond" do assert eval("(cond false :a true :b)") == :b end test "cond with :else" do assert eval("(cond (> 1 2) :a (< 1 2) :b :else :c)") == :b end test "cond falls through to :else" do assert eval("(cond false :a false :b :else :c)") == :c end end describe "case" do test "basic case" do assert eval("(case :ok :ok :yes :error :no)") == :yes end test "case with tuple patterns" do assert eval("(case #el[:ok 42] [:ok val] val [:error _] nil)") == 42 end test "case with wildcard" do assert eval("(case :unknown :ok :yes _ :default)") == :default end end describe "do" do test "do block returns last expression" do assert eval("(do 1 2 3)") == 3 end test "do with side effects" do result = eval("(do (+ 1 2) (+ 3 4))") assert result == 7 end end # --------------------------------------------------------------------------- # 7. loop / recur # --------------------------------------------------------------------------- describe "loop/recur" do test "basic loop" do result = eval("(loop [i 0 acc 0] (if (>= i 5) acc (recur (inc i) (+ acc i))))") assert result == 10 end test "factorial via loop/recur" do result = eval(""" (loop [n 5 acc 1] (if (<= n 1) acc (recur (dec n) (* acc n)))) """) assert result == 120 end test "recur in defn" do {name, mod} = unique_mod("TfTestRecur") source = """ (defmodule #{name} (defn countdown [n] (if (<= n 0) :done (recur (dec n))))) """ eval(source) assert apply(mod, :countdown, [5]) == :done end end # --------------------------------------------------------------------------- # 8. def (top-level binding) # --------------------------------------------------------------------------- describe "def" do test "def creates a zero-arity function" do {name, mod} = unique_mod("TfTestDef") source = "(defmodule #{name} (def answer 42))" eval(source) assert apply(mod, :answer, []) == 42 end end # --------------------------------------------------------------------------- # 9. Module/function calls (FFI) # --------------------------------------------------------------------------- describe "module calls" do test "Elixir module call (uppercase)" do assert eval("(Enum/map [1 2 3] (fn [x] (* x x)))") == [1, 4, 9] end test "Elixir module call with hyphens in function name" do # String.split -> String.split (no hyphens, just testing the pattern) result = eval(~S|(String/split "a-b-c" "-")|) assert result == ["a", "b", "c"] end test "Erlang module call (lowercase)" do result = eval("(erlang/system_time)") assert is_integer(result) end test "Map module call" do result = eval("(Map/put {:a 1} :b 2)") assert result == %{a: 1, b: 2} end test "Enum/reduce" do result = eval("(Enum/reduce [1 2 3] 0 (fn [acc x] (+ acc x)))") assert result == 6 end end # --------------------------------------------------------------------------- # 10. Unqualified function calls # --------------------------------------------------------------------------- describe "unqualified calls" do test "hyphen to underscore conversion" do ast = transform("(my-func arg1)") ast_str = Macro.to_string(ast) assert ast_str =~ "my_func" end test "calling Kernel functions" do assert eval("(length [1 2 3])") == 3 end test "calling rem" do assert eval("(rem 10 3)") == 1 end end # --------------------------------------------------------------------------- # 11. Data literals # --------------------------------------------------------------------------- describe "data literals" do test "map literal" do assert eval("{:a 1 :b 2}") == %{a: 1, b: 2} end test "empty map" do assert eval("{}") == %{} end test "vector in value position becomes list" do assert eval("[1 2 3]") == [1, 2, 3] end test "vector in pattern position becomes tuple match" do assert eval("(case #el[:ok 42] [:ok x] x)") == 42 end test "tuple literal" do assert eval("#el[:ok 42]") == {:ok, 42} end test "tuple with three elements" do assert eval("#el[:a :b :c]") == {:a, :b, :c} end test "set literal" do result = eval(~S|#{:a :b :c}|) assert result == MapSet.new([:a, :b, :c]) end test "nested data structures" do result = eval(~S|{:users [{:name "Ada"} {:name "Bob"}]}|) assert result == %{users: [%{name: "Ada"}, %{name: "Bob"}]} end test "quoted list" do result = eval("(quote (1 2 3))") assert result == [1, 2, 3] end test "regex literal" do ast = transform(~S|#"^\d+$"|) ast_str = Macro.to_string(ast) # Macro.to_string renders sigil_r as ~r/pattern/ assert ast_str =~ "~r" or ast_str =~ "sigil_r" end end # --------------------------------------------------------------------------- # 12. Keyword-as-function # --------------------------------------------------------------------------- describe "keyword-as-function" do test "keyword access on map" do assert eval(~S|(:name {:name "Ada" :age 30})|) == "Ada" end test "keyword access with default" do assert eval(~S|(:missing {:name "Ada"} :default)|) == :default end test "keyword access returns nil on missing key" do assert eval(~S|(:missing {:name "Ada"})|) == nil end end # --------------------------------------------------------------------------- # 13. defprotocol # --------------------------------------------------------------------------- describe "defprotocol" do test "basic protocol definition" do ast = transform(""" (defprotocol Describable (describe [value])) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defprotocol" assert ast_str =~ "describe" end test "protocol with docstring" do ast = transform(""" (defprotocol Describable "Protocol for descriptions" (describe [value])) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defprotocol" assert ast_str =~ "moduledoc" end end # --------------------------------------------------------------------------- # 14. defrecord # --------------------------------------------------------------------------- describe "defrecord" do test "basic record definition" do ast = transform(""" (defrecord User [name age]) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defstruct" assert ast_str =~ "defmodule" end test "record with docstring" do ast = transform(""" (defrecord User "A user" [name age]) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defstruct" assert ast_str =~ "moduledoc" end end # --------------------------------------------------------------------------- # 15. extend-type / extend-protocol # --------------------------------------------------------------------------- describe "extend-type" do test "produces defimpl" do ast = transform(""" (extend-type Map MyProto (-lookup [m k] (Map/get m k))) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defimpl" assert ast_str =~ "MyProto" assert ast_str =~ "Map" end end describe "extend-protocol" do test "produces defimpl" do ast = transform(""" (extend-protocol MyProto Map (-lookup [m k] (Map/get m k))) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defimpl" end end # --------------------------------------------------------------------------- # 16. reify # --------------------------------------------------------------------------- describe "reify" do test "produces defmodule and struct instance" do ast = transform(""" (reify MyProto (-describe [_] "hello")) """) ast_str = Macro.to_string(ast) assert ast_str =~ "defmodule" assert ast_str =~ "defstruct" end end # --------------------------------------------------------------------------- # 17. with # --------------------------------------------------------------------------- describe "with" do test "basic with" do result = eval(""" (with [[:ok x] #el[:ok 42]] x) """) assert result == 42 end test "with multiple bindings" do result = eval(""" (with [[:ok a] #el[:ok 1] [:ok b] #el[:ok 2]] (+ a b)) """) assert result == 3 end test "with short-circuit on mismatch" do result = eval(""" (with [[:ok x] #el[:error :oops]] x) """) assert result == {:error, :oops} end end # --------------------------------------------------------------------------- # 18. receive # --------------------------------------------------------------------------- describe "receive" do test "produces receive AST" do ast = transform(""" (receive [:ok val] val [:error _] nil) """) ast_str = Macro.to_string(ast) assert ast_str =~ "receive" end test "receive with after" do ast = transform(""" (receive [:ok val] val :after 1000 :timeout) """) ast_str = Macro.to_string(ast) assert ast_str =~ "receive" assert ast_str =~ "after" end test "receive with timeout evaluates" do # Send ourselves a message, then receive it result = eval(""" (do (Kernel/send *self* #el[:ok 42]) (receive [:ok val] val :after 100 :timeout)) """) assert result == 42 end test "receive timeout fires when no message" do result = eval(""" (receive [:ok val] val :after 1 :timeout) """) assert result == :timeout end end # --------------------------------------------------------------------------- # 18b. send/spawn/spawn-link (bare unqualified calls) # --------------------------------------------------------------------------- describe "send/spawn/spawn-link" do test "send delivers message to self" do result = eval(""" (do (send *self* :hello) (receive :hello :got-it :after 100 :timeout)) """) assert result == :"got-it" end test "send with tuple message" do result = eval(""" (do (send *self* #el[:ok 42]) (receive [:ok val] val :after 100 :timeout)) """) assert result == 42 end test "spawn creates a process" do result = eval(""" (do (let [pid (spawn (fn [] :done))] (is-pid pid))) """) assert result == true end test "spawn and send between processes" do result = eval(""" (do (let [parent *self* child (spawn (fn [] (send parent #el[:from-child 99]) :done))] (receive [:from-child val] val :after 1000 :timeout))) """) assert result == 99 end test "spawn-link creates a linked process" do result = eval(""" (do (let [pid (spawn-link (fn [] :done))] (is-pid pid))) """) assert result == true end test "spawn with module/function/args" do # spawn/3 with module, function, args ast = transform("(spawn Kernel :is-integer '(42))") ast_str = Macro.to_string(ast) assert ast_str =~ "spawn" end end # --------------------------------------------------------------------------- # 18c. Process primitives: monitor, link, unlink, alive? # --------------------------------------------------------------------------- describe "process primitives" do test "link/unlink" do result = eval(""" (do (let [pid (spawn (fn [] (receive _ :ok :after 1000 :done)))] (link pid) (unlink pid) (alive? pid))) """) assert result == true end test "monitor produces reference" do result = eval(""" (do (let [pid (spawn (fn [] (receive _ :ok :after 1000 :done))) ref (monitor pid)] (is-reference ref))) """) assert result == true end test "monitor with type" do result = eval(""" (do (let [pid (spawn (fn [] :done)) ref (monitor :process pid)] (is-reference ref))) """) assert result == true end test "alive? returns boolean" do result = eval(""" (do (let [pid (spawn (fn [] :done))] (Process/sleep 50) (alive? pid))) """) assert result == false end end # --------------------------------------------------------------------------- # 19. for / doseq # --------------------------------------------------------------------------- describe "for" do test "basic for comprehension" do result = eval("(for [x [1 2 3]] (* x x))") assert result == [1, 4, 9] end test "for with :when filter" do result = eval("(for [x [1 2 3 4 5] :when (> x 2)] x)") assert result == [3, 4, 5] end test "for with multiple generators" do result = eval("(for [x [1 2] y [3 4]] (+ x y))") assert result == [4, 5, 5, 6] end end describe "doseq" do test "doseq produces for AST" do ast = transform("(doseq [x [1 2 3]] (println x))") ast_str = Macro.to_string(ast) assert ast_str =~ "for" end end # --------------------------------------------------------------------------- # 20. if-let / when-let / if-some / when-some # --------------------------------------------------------------------------- describe "if-let" do test "if-let with truthy value" do result = eval("(if-let [x 42] x :nope)") assert result == 42 end test "if-let with nil" do result = eval("(if-let [x nil] x :nope)") assert result == :nope end test "if-let with false" do result = eval("(if-let [x false] x :nope)") assert result == :nope end end describe "when-let" do test "when-let with truthy value" do result = eval("(when-let [x 42] (+ x 1))") assert result == 43 end test "when-let with nil returns nil" do result = eval("(when-let [x nil] (+ x 1))") assert result == nil end end describe "if-some" do test "if-some with non-nil value" do result = eval("(if-some [x 42] x :nope)") assert result == 42 end test "if-some with false (not nil)" do result = eval("(if-some [x false] x :nope)") assert result == false end test "if-some with nil" do result = eval("(if-some [x nil] x :nope)") assert result == :nope end end describe "when-some" do test "when-some with non-nil value" do result = eval("(when-some [x 42] (+ x 1))") assert result == 43 end test "when-some with nil returns nil" do result = eval("(when-some [x nil] (+ x 1))") assert result == nil end test "when-some with false (non-nil)" do # false is not nil, so the body should execute # Keywords preserve hyphens: :got-it stays as :got-it result = eval("(when-some [x false] :done)") assert result == :done end end # --------------------------------------------------------------------------- # 21. use / require / import / alias # --------------------------------------------------------------------------- describe "directives" do test "use produces use AST" do ast = transform("(use GenServer)") ast_str = Macro.to_string(ast) assert ast_str =~ "use" assert ast_str =~ "GenServer" end test "require produces require AST" do ast = transform("(require Logger)") ast_str = Macro.to_string(ast) assert ast_str =~ "require" assert ast_str =~ "Logger" end test "import produces import AST" do ast = transform("(import Enum)") ast_str = Macro.to_string(ast) assert ast_str =~ "import" assert ast_str =~ "Enum" end test "alias produces alias AST" do ast = transform("(alias MyApp)") ast_str = Macro.to_string(ast) assert ast_str =~ "alias" assert ast_str =~ "MyApp" end end # --------------------------------------------------------------------------- # 22. Operators and builtins # --------------------------------------------------------------------------- describe "arithmetic operators" do test "addition" do assert eval("(+ 1 2)") == 3 end test "addition variadic" do assert eval("(+ 1 2 3 4)") == 10 end test "subtraction" do assert eval("(- 10 3)") == 7 end test "unary minus" do assert eval("(- 5)") == -5 end test "multiplication" do assert eval("(* 3 4)") == 12 end test "multiplication variadic" do assert eval("(* 2 3 4)") == 24 end # Note: / is not a valid symbol start char in the reader, # so division uses the Kernel module call instead test "division via Kernel" do result = eval("(Kernel/div 10 2)") assert result == 5 end end describe "comparison operators" do test "greater than" do assert eval("(> 5 3)") == true assert eval("(> 3 5)") == false end test "less than" do assert eval("(< 3 5)") == true assert eval("(< 5 3)") == false end test "greater than or equal" do assert eval("(>= 5 5)") == true assert eval("(>= 5 6)") == false end test "less than or equal" do assert eval("(<= 5 5)") == true assert eval("(<= 6 5)") == false end end describe "equality" do test "= for value equality" do assert eval("(= 1 1)") == true assert eval("(= 1 2)") == false end test "== for numeric equality" do assert eval("(== 1 1)") == true end test "not=" do assert eval("(not= 1 2)") == true assert eval("(not= 1 1)") == false end test "!=" do assert eval("(!= 1 2)") == true assert eval("(!= 1 1)") == false end end describe "boolean operators" do test "not" do assert eval("(not true)") == false assert eval("(not false)") == true end test "and" do assert eval("(and true true)") == true assert eval("(and true false)") == false end test "or" do assert eval("(or false true)") == true assert eval("(or false false)") == false end test "and variadic" do assert eval("(and true true true)") == true assert eval("(and true false true)") == false end end describe "builtins" do test "inc" do assert eval("(inc 5)") == 6 end test "dec" do assert eval("(dec 5)") == 4 end test "str concatenation" do assert eval(~S|(str "hello" " " "world")|) == "hello world" end test "str single arg" do assert eval(~S|(str 42)|) == "42" end test "str empty" do assert eval("(str)") == "" end test "nil?" do assert eval("(nil? nil)") == true assert eval("(nil? 42)") == false end test "count" do assert eval("(count [1 2 3])") == 3 end test "hd" do assert eval("(hd [1 2 3])") == 1 end test "tl" do assert eval("(tl [1 2 3])") == [2, 3] end test "cons" do assert eval("(cons 0 [1 2 3])") == [0, 1, 2, 3] end test "throw produces raise" do ast = transform(~S|(throw "oops")|) ast_str = Macro.to_string(ast) assert ast_str =~ "raise" end test "println produces IO.puts" do ast = transform(~S|(println "hello")|) ast_str = Macro.to_string(ast) assert ast_str =~ "IO" assert ast_str =~ "puts" end end # --------------------------------------------------------------------------- # 23. Dynamic vars # --------------------------------------------------------------------------- describe "dynamic vars" do test "*self* produces self() call" do ast = transform("*self*") assert match?({:self, _, []}, ast) end test "*node* produces node() call" do ast = transform("*node*") assert match?({:node, _, []}, ast) end test "*self* evaluates to current process" do result = eval("*self*") assert is_pid(result) end end # --------------------------------------------------------------------------- # 24. munge_name # --------------------------------------------------------------------------- describe "munge_name" do test "hyphens to underscores" do assert Transformer.munge_name("my-func") == "my_func" end test "question mark to _qmark" do assert Transformer.munge_name("nil?") == "nil_qmark" end test "exclamation to _bang" do assert Transformer.munge_name("reset!") == "reset_bang" end test "combined hyphen and bang" do assert Transformer.munge_name("do-thing!") == "do_thing_bang" end test "no change for plain names" do assert Transformer.munge_name("hello") == "hello" end test "arrow preserved" do # -> contains a hyphen, but it's part of the arrow assert Transformer.munge_name("map->set") == "map_>set" end end # --------------------------------------------------------------------------- # 25. Symbols as variables # --------------------------------------------------------------------------- describe "symbols" do test "plain symbol becomes variable" do ast = transform("x") assert match?({:x, _, nil}, ast) end test "symbol with hyphens becomes munged variable" do ast = transform("my-var") assert match?({:my_var, _, nil}, ast) end test "true symbol becomes true literal" do assert eval("true") == true end test "false symbol becomes false literal" do assert eval("false") == false end test "nil symbol becomes nil literal" do assert eval("nil") == nil end end # --------------------------------------------------------------------------- # 26. Pattern position vectors -> tuple matches # --------------------------------------------------------------------------- describe "pattern position vectors" do test "vector in case pattern becomes tuple" do result = eval("(case #el[:ok 42] [:ok x] x [:error _] nil)") assert result == 42 end test "vector in let LHS becomes tuple match" do result = eval("(let [[:ok x] #el[:ok 99]] x)") assert result == 99 end test "nested vector patterns in case" do result = eval(""" (case #el[:ok #el[:inner 5]] [:ok [:inner n]] n _ nil) """) assert result == 5 end test "defn params are not in pattern context (they are parameter lists)" do # Params in defn are parameter lists, not patterns for tuple matching # The params vector itself is parameter list, but inner vectors would be patterns {name, mod} = unique_mod("TfTestPatParams") source = """ (defmodule #{name} (defn extract [msg] (case msg [:ok val] val _ nil))) """ eval(source) assert apply(mod, :extract, [{:ok, 42}]) == 42 end end # --------------------------------------------------------------------------- # Integration: end-to-end tests # --------------------------------------------------------------------------- describe "integration" do test "fibonacci with loop/recur" do result = eval(""" (loop [n 10 a 0 b 1] (if (= n 0) a (recur (dec n) b (+ a b)))) """) assert result == 55 end test "map + filter pipeline" do result = eval(""" (Enum/filter (Enum/map [1 2 3 4 5 6 7 8 9 10] (fn [x] (* x x))) (fn [x] (> x 25))) """) assert result == [36, 49, 64, 81, 100] end test "nested let with function calls" do result = eval(""" (let [nums [1 2 3 4 5] doubled (Enum/map nums (fn [x] (* x 2))) total (Enum/reduce doubled 0 (fn [acc x] (+ acc x)))] total) """) assert result == 30 end test "defmodule with multiple features" do {name, mod} = unique_mod("TfTestIntegration") source = """ (defmodule #{name} (defn factorial [n] (loop [i n acc 1] (if (<= i 1) acc (recur (dec i) (* acc i))))) (defn sum-squares [nums] (Enum/reduce (Enum/map nums (fn [x] (* x x))) 0 (fn [acc x] (+ acc x))))) """ eval(source) assert apply(mod, :factorial, [5]) == 120 assert apply(mod, :sum_squares, [[1, 2, 3, 4, 5]]) == 55 end test "keyword access chain" do result = eval(""" (let [person {:name "Ada" :age 30}] (str (:name person) " is " (Kernel/to_string (:age person)))) """) assert result == "Ada is 30" end test "for comprehension with filter" do result = eval(""" (for [x [1 2 3 4 5 6 7 8 9 10] :when (= 0 (rem x 2))] (* x x)) """) assert result == [4, 16, 36, 64, 100] end test "case with multiple patterns in module" do {name, mod} = unique_mod("TfTestCase") source = """ (defmodule #{name} (defn handle [msg] (case msg [:ok data] (str "ok: " (Kernel/to_string data)) [:error reason] (str "error: " (Kernel/to_string reason)) _ "unknown"))) """ eval(source) assert apply(mod, :handle, [{:ok, 42}]) == "ok: 42" assert apply(mod, :handle, [{:error, :bad}]) == "error: bad" assert apply(mod, :handle, [:other]) == "unknown" end test "with chain" do result = eval(""" (with [[:ok a] #el[:ok 1] [:ok b] #el[:ok 2] [:ok c] #el[:ok 3]] (+ a (+ b c))) """) assert result == 6 end test "with chain short-circuits" do result = eval(""" (with [[:ok a] #el[:ok 1] [:ok b] #el[:error :fail] [:ok c] #el[:ok 3]] (+ a (+ b c))) """) assert result == {:error, :fail} end test "anonymous function as value" do result = eval(""" (let [f (fn [x] (* x x))] (Enum/map [1 2 3] f)) """) assert result == [1, 4, 9] end test "nested cond" do result = eval(""" (let [x 15] (cond (> x 20) :high (> x 10) :medium :else :low)) """) assert result == :medium end end # --------------------------------------------------------------------------- # Literals passthrough # --------------------------------------------------------------------------- describe "literals" do test "integers" do assert eval("42") == 42 assert eval("-7") == -7 end test "floats" do assert eval("3.14") == 3.14 end test "strings" do assert eval(~S|"hello"|) == "hello" end test "keywords" do assert eval(":ok") == :ok assert eval(":error") == :error end test "booleans" do assert eval("true") == true assert eval("false") == false end test "nil" do assert eval("nil") == nil end end # --------------------------------------------------------------------------- # Context struct # --------------------------------------------------------------------------- describe "Context" do test "default context" do ctx = %Context{} assert ctx.module_name == nil assert ctx.function_name == nil assert ctx.function_arity == nil assert ctx.loop_var == nil assert ctx.loop_arity == nil assert ctx.in_pattern == false assert ctx.records == %{} assert ctx.gensym_counter == 0 end end end