init commit
This commit is contained in:
@@ -8,4 +8,6 @@ erl_crash.dump
|
||||
*.beam
|
||||
/tmp/
|
||||
.elixir_ls/
|
||||
.clj-kondo/
|
||||
.lsp/
|
||||
.nrepl-port
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
{:tasks
|
||||
{compile {:doc "Compile all .clje and .ex files"
|
||||
:task (shell "mix compile")}
|
||||
:task (apply shell "mix compile" *command-line-args*)}
|
||||
|
||||
test {:doc "Run all tests"
|
||||
:task (shell "mix test")}
|
||||
|
||||
test:trace {:doc "Run all tests with trace output"
|
||||
:task (shell "mix test --trace")}
|
||||
test {:doc "Run tests (accepts mix test args, e.g. bb test --only phase5)"
|
||||
:task (apply shell "mix test" *command-line-args*)}
|
||||
|
||||
repl {:doc "Start interactive CljElixir REPL"
|
||||
:task (shell "rlwrap mix clje.repl")}
|
||||
@@ -14,11 +11,17 @@
|
||||
repl:basic {:doc "Start REPL without rlwrap"
|
||||
:task (shell "mix clje.repl")}
|
||||
|
||||
nrepl {:doc "Start nREPL server (random port)"
|
||||
:task (shell "mix clje.nrepl")}
|
||||
nrepl {:doc "Start nREPL server (bb nrepl --port 7888)"
|
||||
:task (apply shell "mix clje.nrepl" *command-line-args*)}
|
||||
|
||||
nrepl:port {:doc "Start nREPL on port 7888"
|
||||
:task (shell "mix clje.nrepl --port 7888")}
|
||||
eval {:doc "Evaluate expression (bb eval '(+ 1 2)')"
|
||||
:task (apply shell "mix clje.eval" *command-line-args*)}
|
||||
|
||||
run {:doc "Run a .clje file (bb run examples/chat_room.clje)"
|
||||
:task (apply shell "mix clje.run" *command-line-args*)}
|
||||
|
||||
build {:doc "Compile .clje to BEAM (bb build src/foo.clje [-o dir])"
|
||||
:task (apply shell "mix clje.build" *command-line-args*)}
|
||||
|
||||
clean {:doc "Clean build artifacts"
|
||||
:task (shell "mix clean")}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# PersistentVector vs Erlang Tuple vs List benchmarks
|
||||
# Run: mix run bench/persistent_vector_bench.exs
|
||||
|
||||
defmodule PVBench do
|
||||
def run do
|
||||
sizes = [10, 100, 1_000, 10_000]
|
||||
|
||||
IO.puts("=" |> String.duplicate(70))
|
||||
IO.puts("PersistentVector vs Tuple vs List — Benchmarks")
|
||||
IO.puts("=" |> String.duplicate(70))
|
||||
|
||||
for size <- sizes do
|
||||
IO.puts("\n--- Size: #{size} ---\n")
|
||||
list = Enum.to_list(1..size)
|
||||
tuple = List.to_tuple(list)
|
||||
pv = CljElixir.PersistentVector.from_list(list)
|
||||
|
||||
bench_construction(size, list)
|
||||
bench_indexed_access(size, list, tuple, pv)
|
||||
bench_append(size, list, tuple, pv)
|
||||
bench_update(size, list, tuple, pv)
|
||||
bench_iteration(size, list, tuple, pv)
|
||||
end
|
||||
end
|
||||
|
||||
defp bench_construction(size, list) do
|
||||
{pv_us, _} = :timer.tc(fn ->
|
||||
CljElixir.PersistentVector.from_list(list)
|
||||
end)
|
||||
|
||||
{tuple_us, _} = :timer.tc(fn ->
|
||||
List.to_tuple(list)
|
||||
end)
|
||||
|
||||
IO.puts(" Construction:")
|
||||
IO.puts(" PersistentVector.from_list: #{format_us(pv_us)}")
|
||||
IO.puts(" List.to_tuple: #{format_us(tuple_us)}")
|
||||
IO.puts(" List (identity): ~0 µs")
|
||||
end
|
||||
|
||||
defp bench_indexed_access(size, list, tuple, pv) do
|
||||
indices = for _ <- 1..1000, do: :rand.uniform(size) - 1
|
||||
|
||||
{pv_us, _} = :timer.tc(fn ->
|
||||
Enum.each(indices, fn i -> CljElixir.PersistentVector.pv_nth(pv, i) end)
|
||||
end)
|
||||
|
||||
{tuple_us, _} = :timer.tc(fn ->
|
||||
Enum.each(indices, fn i -> elem(tuple, i) end)
|
||||
end)
|
||||
|
||||
{list_us, _} = :timer.tc(fn ->
|
||||
Enum.each(indices, fn i -> Enum.at(list, i) end)
|
||||
end)
|
||||
|
||||
IO.puts(" Indexed access (1000 random):")
|
||||
IO.puts(" PersistentVector.pv_nth: #{format_us(pv_us)}")
|
||||
IO.puts(" elem(tuple, i): #{format_us(tuple_us)}")
|
||||
IO.puts(" Enum.at(list, i): #{format_us(list_us)}")
|
||||
end
|
||||
|
||||
defp bench_append(size, _list, tuple, pv) do
|
||||
iters = min(1000, size)
|
||||
|
||||
{pv_us, _} = :timer.tc(fn ->
|
||||
Enum.reduce(1..iters, pv, fn x, acc ->
|
||||
CljElixir.PersistentVector.pv_conj(acc, x)
|
||||
end)
|
||||
end)
|
||||
|
||||
{tuple_us, _} = :timer.tc(fn ->
|
||||
Enum.reduce(1..iters, tuple, fn x, acc ->
|
||||
:erlang.append_element(acc, x)
|
||||
end)
|
||||
end)
|
||||
|
||||
{list_us, _} = :timer.tc(fn ->
|
||||
# Prepend (O(1)) — append would be O(n) per op
|
||||
Enum.reduce(1..iters, [], fn x, acc -> [x | acc] end)
|
||||
end)
|
||||
|
||||
IO.puts(" Append #{iters} elements:")
|
||||
IO.puts(" PersistentVector.pv_conj: #{format_us(pv_us)}")
|
||||
IO.puts(" :erlang.append_element: #{format_us(tuple_us)}")
|
||||
IO.puts(" [x | list] (prepend): #{format_us(list_us)}")
|
||||
end
|
||||
|
||||
defp bench_update(size, _list, tuple, pv) do
|
||||
indices = for _ <- 1..1000, do: :rand.uniform(size) - 1
|
||||
|
||||
{pv_us, _} = :timer.tc(fn ->
|
||||
Enum.each(indices, fn i ->
|
||||
CljElixir.PersistentVector.pv_assoc(pv, i, :updated)
|
||||
end)
|
||||
end)
|
||||
|
||||
{tuple_us, _} = :timer.tc(fn ->
|
||||
Enum.each(indices, fn i ->
|
||||
put_elem(tuple, i, :updated)
|
||||
end)
|
||||
end)
|
||||
|
||||
IO.puts(" Update (1000 random):")
|
||||
IO.puts(" PersistentVector.pv_assoc: #{format_us(pv_us)}")
|
||||
IO.puts(" put_elem(tuple, i, v): #{format_us(tuple_us)}")
|
||||
IO.puts(" List: N/A (O(n) per update)")
|
||||
end
|
||||
|
||||
defp bench_iteration(size, list, tuple, pv) do
|
||||
{pv_us, _} = :timer.tc(fn ->
|
||||
cnt = CljElixir.PersistentVector.pv_count(pv)
|
||||
Enum.reduce(0..(cnt - 1), 0, fn i, acc ->
|
||||
acc + CljElixir.PersistentVector.pv_nth(pv, i)
|
||||
end)
|
||||
end)
|
||||
|
||||
{tuple_us, _} = :timer.tc(fn ->
|
||||
Enum.reduce(0..(size - 1), 0, fn i, acc ->
|
||||
acc + elem(tuple, i)
|
||||
end)
|
||||
end)
|
||||
|
||||
{list_us, _} = :timer.tc(fn ->
|
||||
Enum.reduce(list, 0, fn x, acc -> acc + x end)
|
||||
end)
|
||||
|
||||
IO.puts(" Iteration (sum all):")
|
||||
IO.puts(" PersistentVector by index: #{format_us(pv_us)}")
|
||||
IO.puts(" Tuple by index: #{format_us(tuple_us)}")
|
||||
IO.puts(" List reduce: #{format_us(list_us)}")
|
||||
end
|
||||
|
||||
defp format_us(us) when us < 1_000, do: "#{us} µs"
|
||||
defp format_us(us) when us < 1_000_000, do: "#{Float.round(us / 1_000, 1)} ms"
|
||||
defp format_us(us), do: "#{Float.round(us / 1_000_000, 2)} s"
|
||||
end
|
||||
|
||||
PVBench.run()
|
||||
@@ -0,0 +1,142 @@
|
||||
;;; ChatRoom — process-based chat room using spawn/send/receive
|
||||
;;;
|
||||
;;; This is a single-VM demo showing BEAM concurrency primitives.
|
||||
;;; Run with: mix clje.run examples/chat_room.clje
|
||||
;;;
|
||||
;;; For a multi-terminal chat experience, see:
|
||||
;;; examples/tcp_chat_server.clje and examples/tcp_chat_client.clje
|
||||
|
||||
(ns ChatRoom
|
||||
(:require [clje.core :refer :all]
|
||||
[Enum] [IO] [Map] [Process] [String]))
|
||||
|
||||
;; ── Room loop ─────────────────────────────────────────────────────
|
||||
;; Manages members map of {username → pid} and broadcasts messages.
|
||||
|
||||
(defn run-loop [state]
|
||||
(receive
|
||||
[:join username pid]
|
||||
(let [members (assoc (:members state) username pid)]
|
||||
(send pid #el[:welcome username (count members)])
|
||||
;; Notify existing members
|
||||
(doseq [[_name member-pid] (:members state)]
|
||||
(send member-pid #el[:system (str username " joined the room")]))
|
||||
(recur (assoc state :members members)))
|
||||
|
||||
[:message from body] :guard [(is-binary body)]
|
||||
(do
|
||||
(doseq [[_name pid] (:members state)]
|
||||
(send pid #el[:chat from body]))
|
||||
(recur state))
|
||||
|
||||
[:leave username]
|
||||
(let [new-members (dissoc (:members state) username)]
|
||||
(doseq [[_name pid] new-members]
|
||||
(send pid #el[:system (str username " left the room")]))
|
||||
(recur (assoc state :members new-members)))
|
||||
|
||||
[:who reply-pid]
|
||||
(do
|
||||
(send reply-pid #el[:members (Map/keys (:members state))])
|
||||
(recur state))
|
||||
|
||||
:shutdown
|
||||
(do
|
||||
(doseq [[_name pid] (:members state)]
|
||||
(send pid :room-closed))
|
||||
:ok)
|
||||
|
||||
:after 5000
|
||||
(if (== (count (:members state)) 0)
|
||||
:empty-timeout
|
||||
(recur state))))
|
||||
|
||||
;; ── User listener ─────────────────────────────────────────────────
|
||||
;; Collects messages received by a user and prints them.
|
||||
|
||||
(defn listen [username messages]
|
||||
(receive
|
||||
[:welcome _name member-count]
|
||||
(do
|
||||
(println (str " [" username " sees: Welcome! " member-count " user(s) here]"))
|
||||
(ChatRoom/listen username messages))
|
||||
|
||||
[:chat from body]
|
||||
(do
|
||||
(println (str " [" username " sees: " from "> " body "]"))
|
||||
(ChatRoom/listen username (cons #el[from body] messages)))
|
||||
|
||||
[:system text]
|
||||
(do
|
||||
(println (str " [" username " sees: * " text "]"))
|
||||
(ChatRoom/listen username messages))
|
||||
|
||||
:room-closed
|
||||
(println (str " [" username " sees: room closed]"))
|
||||
|
||||
:dump
|
||||
messages
|
||||
|
||||
:after 2000
|
||||
(do
|
||||
(println (str " [" username " done listening]"))
|
||||
messages)))
|
||||
|
||||
;; ── Run the demo ────────────────────────────────────────────────────
|
||||
|
||||
(println "=== ChatRoom Demo ===\n")
|
||||
|
||||
;; Start the room
|
||||
(let [room (spawn (fn [] (ChatRoom/run-loop {:owner "system" :members {}})))
|
||||
alice-listener (spawn (fn [] (ChatRoom/listen "alice" (list))))
|
||||
bob-listener (spawn (fn [] (ChatRoom/listen "bob" (list))))
|
||||
carol-listener (spawn (fn [] (ChatRoom/listen "carol" (list))))]
|
||||
|
||||
;; Alice joins
|
||||
(println "Alice joins...")
|
||||
(send room #el[:join "alice" alice-listener])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Bob joins
|
||||
(println "\nBob joins...")
|
||||
(send room #el[:join "bob" bob-listener])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Alice sends a message
|
||||
(println "\nAlice sends a message...")
|
||||
(send room #el[:message "alice" "Hello everyone!"])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Carol joins
|
||||
(println "\nCarol joins...")
|
||||
(send room #el[:join "carol" carol-listener])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Bob sends a message
|
||||
(println "\nBob sends a message...")
|
||||
(send room #el[:message "bob" "Hey Alice! Welcome Carol!"])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Carol sends a message
|
||||
(println "\nCarol sends a message...")
|
||||
(send room #el[:message "carol" "Thanks Bob!"])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Check who's online
|
||||
(println "\nWho's online?")
|
||||
(send room #el[:who *self*])
|
||||
(receive
|
||||
[:members names]
|
||||
(println (str " Online: " (Enum/join names ", "))))
|
||||
|
||||
;; Bob leaves
|
||||
(println "\nBob leaves...")
|
||||
(send room #el[:leave "bob"])
|
||||
(Process/sleep 100)
|
||||
|
||||
;; Shutdown the room
|
||||
(println "\nShutting down room...")
|
||||
(send room :shutdown)
|
||||
(Process/sleep 200)
|
||||
|
||||
(println "\n=== Demo complete ==="))
|
||||
@@ -0,0 +1,130 @@
|
||||
;;; TCP Chat Client — connects to the TCP chat server
|
||||
;;;
|
||||
;;; Usage:
|
||||
;;; mix clje.run --no-halt examples/tcp_chat_client.clje -- <username>
|
||||
;;;
|
||||
;;; The server must be running first:
|
||||
;;; mix clje.run --no-halt examples/tcp_chat_server.clje
|
||||
;;;
|
||||
;;; Commands:
|
||||
;;; Type a message and press Enter to send
|
||||
;;; /who — list online users
|
||||
;;; /quit — disconnect and exit
|
||||
|
||||
(ns TcpChatClient
|
||||
(:require [clje.core :refer :all]
|
||||
[IO] [String] [System] [erlang] [gen_tcp]))
|
||||
|
||||
;; ── Receiver process ──────────────────────────────────────────────
|
||||
;; Listens for TCP messages from the server and prints them.
|
||||
|
||||
(defn receiver [socket parent]
|
||||
(receive
|
||||
[:tcp _sock data]
|
||||
(let [line (String/trim data)]
|
||||
(cond
|
||||
(String/starts-with? line "MSG:")
|
||||
(let [rest (String/slice line 4 (String/length line))
|
||||
parts (String/split rest ":" (list #el[:parts 2]))
|
||||
from (hd parts)
|
||||
text (hd (tl parts))]
|
||||
(IO/puts (str from "> " text))
|
||||
(TcpChatClient/receiver socket parent))
|
||||
|
||||
(String/starts-with? line "SYS:")
|
||||
(do
|
||||
(IO/puts (str "* " (String/slice line 4 (String/length line))))
|
||||
(TcpChatClient/receiver socket parent))
|
||||
|
||||
(String/starts-with? line "JOIN:")
|
||||
(do
|
||||
(IO/puts (str "* " (String/slice line 5 (String/length line)) " joined"))
|
||||
(TcpChatClient/receiver socket parent))
|
||||
|
||||
(String/starts-with? line "QUIT:")
|
||||
(do
|
||||
(IO/puts (str "* " (String/slice line 5 (String/length line)) " left"))
|
||||
(TcpChatClient/receiver socket parent))
|
||||
|
||||
:else
|
||||
(do
|
||||
(IO/puts line)
|
||||
(TcpChatClient/receiver socket parent))))
|
||||
|
||||
[:tcp_closed _sock]
|
||||
(do
|
||||
(IO/puts "* Connection closed by server.")
|
||||
(System/halt 0))
|
||||
|
||||
[:tcp_error _sock _reason]
|
||||
(do
|
||||
(IO/puts "* Connection error.")
|
||||
(System/halt 1))))
|
||||
|
||||
;; ── Input loop ────────────────────────────────────────────────────
|
||||
;; Reads from stdin and sends to the server.
|
||||
|
||||
(defn send-line [socket line]
|
||||
(gen_tcp/send socket line))
|
||||
|
||||
(defn input-loop [socket]
|
||||
(let [line (IO/gets "")]
|
||||
(cond
|
||||
(== line :eof)
|
||||
(do
|
||||
(TcpChatClient/send-line socket "QUIT\n")
|
||||
(gen_tcp/close socket)
|
||||
(System/halt 0))
|
||||
|
||||
:else
|
||||
(let [trimmed (String/trim line)]
|
||||
(cond
|
||||
(== trimmed "/quit")
|
||||
(do
|
||||
(TcpChatClient/send-line socket "QUIT\n")
|
||||
(gen_tcp/close socket)
|
||||
(IO/puts "Goodbye!")
|
||||
(System/halt 0))
|
||||
|
||||
(== trimmed "")
|
||||
(TcpChatClient/input-loop socket)
|
||||
|
||||
:else
|
||||
(do
|
||||
(TcpChatClient/send-line socket (str "MSG:" trimmed "\n"))
|
||||
(TcpChatClient/input-loop socket)))))))
|
||||
|
||||
;; ── Connect ───────────────────────────────────────────────────────
|
||||
|
||||
(defn start [username]
|
||||
(case (gen_tcp/connect (erlang/binary-to-list "127.0.0.1") 4040
|
||||
(list :binary #el[:active true] #el[:packet :line]))
|
||||
[:ok socket]
|
||||
(do
|
||||
;; Send JOIN
|
||||
(TcpChatClient/send-line socket (str "JOIN:" username "\n"))
|
||||
|
||||
;; Spawn receiver and hand it the socket
|
||||
(let [me *self*
|
||||
recv-pid (spawn (fn [] (TcpChatClient/receiver socket me)))]
|
||||
(gen_tcp/controlling-process socket recv-pid)
|
||||
|
||||
;; Run input loop in the main process
|
||||
(IO/puts (str "Connected as " username ". Type a message or /quit to exit."))
|
||||
(TcpChatClient/input-loop socket)))
|
||||
|
||||
[:error reason]
|
||||
(do
|
||||
(IO/puts (str "Could not connect: " reason))
|
||||
(IO/puts "Is the server running? Start it with:")
|
||||
(IO/puts " mix clje.run --no-halt examples/tcp_chat_server.clje")
|
||||
(System/halt 1))))
|
||||
|
||||
;; ── Entry point ─────────────────────────────────────────────────────
|
||||
|
||||
(let [args (System/argv)]
|
||||
(if (== (count args) 0)
|
||||
(do
|
||||
(IO/puts "Usage: mix clje.run --no-halt examples/tcp_chat_client.clje -- <username>")
|
||||
(System/halt 1))
|
||||
(TcpChatClient/start (hd args))))
|
||||
@@ -0,0 +1,131 @@
|
||||
;;; TCP Chat Server — multi-terminal chat using gen_tcp
|
||||
;;;
|
||||
;;; Start the server:
|
||||
;;; mix clje.run --no-halt examples/tcp_chat_server.clje
|
||||
;;;
|
||||
;;; Then connect clients in separate terminals:
|
||||
;;; mix clje.run --no-halt examples/tcp_chat_client.clje -- alice
|
||||
;;; mix clje.run --no-halt examples/tcp_chat_client.clje -- bob
|
||||
;;;
|
||||
;;; Protocol (line-based):
|
||||
;;; Client → Server: JOIN:<username> | MSG:<text> | QUIT
|
||||
;;; Server → Client: SYS:<text> | MSG:<user>:<text> | JOIN:<user> | QUIT:<user>
|
||||
|
||||
(ns TcpChatServer
|
||||
(:require [clje.core :refer :all]
|
||||
[Map] [String] [gen_tcp] [inet]))
|
||||
|
||||
;; ── Room manager ──────────────────────────────────────────────────
|
||||
;; Holds {username → socket} map, broadcasts messages to all members.
|
||||
|
||||
(defn room-loop [members]
|
||||
(receive
|
||||
[:join username socket handler-pid]
|
||||
(do
|
||||
(TcpChatServer/broadcast members (str "JOIN:" username "\n"))
|
||||
(TcpChatServer/send-line socket (str "SYS:Welcome " username "! " (count members) " user(s) online.\n"))
|
||||
(TcpChatServer/room-loop (assoc members username #el[socket handler-pid])))
|
||||
|
||||
[:msg username text]
|
||||
(do
|
||||
(TcpChatServer/broadcast members (str "MSG:" username ":" text "\n"))
|
||||
(TcpChatServer/room-loop members))
|
||||
|
||||
[:quit username]
|
||||
(let [new-members (dissoc members username)]
|
||||
(TcpChatServer/broadcast new-members (str "QUIT:" username "\n"))
|
||||
(TcpChatServer/room-loop new-members))
|
||||
|
||||
[:list reply-pid]
|
||||
(do
|
||||
(send reply-pid #el[:members (Map/keys members)])
|
||||
(TcpChatServer/room-loop members))))
|
||||
|
||||
(defn broadcast [members line]
|
||||
(doseq [[_name [socket _pid]] members]
|
||||
(gen_tcp/send socket line)))
|
||||
|
||||
(defn send-line [socket line]
|
||||
(gen_tcp/send socket line))
|
||||
|
||||
;; ── Per-client handler ────────────────────────────────────────────
|
||||
;; Receives TCP data via active mode and forwards to room manager.
|
||||
|
||||
(defn client-handler [socket room username]
|
||||
(receive
|
||||
[:tcp _sock data]
|
||||
(let [line (String/trim data)]
|
||||
(cond
|
||||
(String/starts-with? line "MSG:")
|
||||
(do
|
||||
(send room #el[:msg username (String/slice line 4 (String/length line))])
|
||||
(TcpChatServer/client-handler socket room username))
|
||||
|
||||
(== line "QUIT")
|
||||
(do
|
||||
(send room #el[:quit username])
|
||||
(gen_tcp/close socket)
|
||||
(println (str " [" username " quit]")))
|
||||
|
||||
:else
|
||||
(do
|
||||
(TcpChatServer/send-line socket (str "SYS:Unknown command. Send MSG:<text> or QUIT\n"))
|
||||
(TcpChatServer/client-handler socket room username))))
|
||||
|
||||
[:tcp_closed _sock]
|
||||
(do
|
||||
(send room #el[:quit username])
|
||||
(println (str " [" username " disconnected]")))
|
||||
|
||||
[:tcp_error _sock _reason]
|
||||
(do
|
||||
(send room #el[:quit username])
|
||||
(println (str " [" username " error]")))))
|
||||
|
||||
;; ── Accept loop ───────────────────────────────────────────────────
|
||||
|
||||
(defn accept-loop [listen-socket room]
|
||||
(case (gen_tcp/accept listen-socket)
|
||||
[:ok client-socket]
|
||||
(do
|
||||
(TcpChatServer/handle-new-client client-socket room)
|
||||
(TcpChatServer/accept-loop listen-socket room))
|
||||
[:error reason]
|
||||
(println (str "Accept error: " reason))))
|
||||
|
||||
(defn handle-new-client [socket room]
|
||||
;; First message must be JOIN:<username>
|
||||
;; Socket starts in passive mode so we can recv synchronously
|
||||
(case (gen_tcp/recv socket 0 5000)
|
||||
[:ok data]
|
||||
(let [line (String/trim data)]
|
||||
(if (String/starts-with? line "JOIN:")
|
||||
(let [username (String/slice line 5 (String/length line))
|
||||
;; Switch to active mode for the handler
|
||||
_ (inet/setopts socket (list #el[:active true]))
|
||||
handler (spawn (fn []
|
||||
(TcpChatServer/client-handler socket room username)))]
|
||||
(gen_tcp/controlling-process socket handler)
|
||||
(send room #el[:join username socket handler])
|
||||
(println (str " [" username " connected]")))
|
||||
(do
|
||||
(TcpChatServer/send-line socket "SYS:First message must be JOIN:<username>\n")
|
||||
(gen_tcp/close socket))))
|
||||
[:error _reason]
|
||||
(gen_tcp/close socket)))
|
||||
|
||||
;; ── Start ─────────────────────────────────────────────────────────
|
||||
|
||||
(defn start [port]
|
||||
(case (gen_tcp/listen port (list :binary #el[:active false] #el[:packet :line] #el[:reuseaddr true]))
|
||||
[:ok listen-socket]
|
||||
(do
|
||||
(println (str "TCP Chat Server listening on port " port))
|
||||
(println "Connect with: mix clje.run --no-halt examples/tcp_chat_client.clje -- <username>")
|
||||
(let [room (spawn (fn [] (TcpChatServer/room-loop {})))]
|
||||
(TcpChatServer/accept-loop listen-socket room)))
|
||||
[:error reason]
|
||||
(println (str "Failed to listen on port " port ": " reason))))
|
||||
|
||||
;; Start the server on port 4040
|
||||
(TcpChatServer/start 4040)
|
||||
@@ -100,6 +100,10 @@ defmodule CljElixir.Analyzer do
|
||||
validate_loop(args, meta, ctx)
|
||||
end
|
||||
|
||||
defp validate_form({:list, _meta, [{:symbol, _, "receive"} | args]}, ctx) do
|
||||
validate_receive(args, ctx)
|
||||
end
|
||||
|
||||
defp validate_form({:list, meta, [{:symbol, _, "recur"} | _args]}, ctx) do
|
||||
validate_recur(meta, ctx)
|
||||
end
|
||||
@@ -529,6 +533,27 @@ defmodule CljElixir.Analyzer do
|
||||
end
|
||||
end
|
||||
|
||||
# receive propagates tail position into clause bodies
|
||||
defp validate_receive(clauses, ctx) do
|
||||
validate_receive_clauses(clauses, ctx)
|
||||
end
|
||||
|
||||
defp validate_receive_clauses([], _ctx), do: []
|
||||
|
||||
defp validate_receive_clauses([:after, _timeout, body | rest], ctx) do
|
||||
validate_form(body, ctx) ++ validate_receive_clauses(rest, ctx)
|
||||
end
|
||||
|
||||
defp validate_receive_clauses([_pattern, :guard, _guard, body | rest], ctx) do
|
||||
validate_form(body, ctx) ++ validate_receive_clauses(rest, ctx)
|
||||
end
|
||||
|
||||
defp validate_receive_clauses([_pattern, body | rest], ctx) do
|
||||
validate_form(body, ctx) ++ validate_receive_clauses(rest, ctx)
|
||||
end
|
||||
|
||||
defp validate_receive_clauses([_], _ctx), do: []
|
||||
|
||||
defp validate_recur(meta, ctx) do
|
||||
line = meta_line(meta)
|
||||
col = meta_col(meta)
|
||||
|
||||
+39
-19
@@ -83,26 +83,46 @@ defmodule CljElixir.Compiler do
|
||||
@spec eval_string(String.t(), keyword()) :: {:ok, term(), keyword()} | {:error, list()}
|
||||
def eval_string(source, opts \\ []) do
|
||||
with {:ok, ast} <- compile_string(source, opts) do
|
||||
try do
|
||||
bindings = opts[:bindings] || []
|
||||
env_opts = build_eval_opts(opts)
|
||||
{result, new_bindings} = Code.eval_quoted(ast, bindings, env_opts)
|
||||
{:ok, result, new_bindings}
|
||||
rescue
|
||||
e ->
|
||||
file = opts[:file] || "nofile"
|
||||
eval_ast(ast, opts)
|
||||
end
|
||||
end
|
||||
|
||||
{:error,
|
||||
[
|
||||
%{
|
||||
severity: :error,
|
||||
message: format_eval_error(e),
|
||||
file: file,
|
||||
line: extract_line(e),
|
||||
col: 0
|
||||
}
|
||||
]}
|
||||
end
|
||||
@doc """
|
||||
Evaluate pre-parsed CljElixir AST forms.
|
||||
|
||||
Runs analyze → transform → eval, skipping the read step.
|
||||
Used by the REPL for incremental re-evaluation of accumulated definitions.
|
||||
|
||||
Returns `{:ok, result, bindings}` on success, or `{:error, diagnostics}` on failure.
|
||||
"""
|
||||
@spec eval_forms(list(), keyword()) :: {:ok, term(), keyword()} | {:error, list()}
|
||||
def eval_forms(forms, opts \\ []) do
|
||||
with {:ok, forms} <- analyze(forms, opts),
|
||||
{:ok, ast} <- transform(forms, opts) do
|
||||
eval_ast(ast, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp eval_ast(ast, opts) do
|
||||
try do
|
||||
bindings = opts[:bindings] || []
|
||||
env_opts = build_eval_opts(opts)
|
||||
{result, new_bindings} = Code.eval_quoted(ast, bindings, env_opts)
|
||||
{:ok, result, new_bindings}
|
||||
rescue
|
||||
e ->
|
||||
file = opts[:file] || "nofile"
|
||||
|
||||
{:error,
|
||||
[
|
||||
%{
|
||||
severity: :error,
|
||||
message: format_eval_error(e),
|
||||
file: file,
|
||||
line: extract_line(e),
|
||||
col: 0
|
||||
}
|
||||
]}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule CljElixir.NRepl.Handler do
|
||||
code = Map.get(msg, "code", "")
|
||||
|
||||
# Capture stdout inside the Agent process where eval actually runs
|
||||
{output, result} = SessionManager.eval_with_capture(manager, session, code)
|
||||
{output, result, ns} = SessionManager.eval_with_capture(manager, session, code)
|
||||
|
||||
responses = []
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule CljElixir.NRepl.Handler do
|
||||
responses =
|
||||
case result do
|
||||
{:ok, value} ->
|
||||
responses ++ [%{"id" => id, "session" => session, "value" => value, "ns" => "user"}]
|
||||
responses ++ [%{"id" => id, "session" => session, "value" => value, "ns" => ns}]
|
||||
|
||||
{:error, error} ->
|
||||
responses ++
|
||||
|
||||
@@ -87,21 +87,22 @@ defmodule CljElixir.NRepl.SessionManager do
|
||||
def handle_call({:eval_with_capture, id, code}, _from, state) do
|
||||
case Map.get(state.sessions, id) do
|
||||
nil ->
|
||||
{:reply, {"", {:error, "unknown session"}}, state}
|
||||
{:reply, {"", {:error, "unknown session"}, "user"}, state}
|
||||
|
||||
pid ->
|
||||
{output, result} =
|
||||
{output, result, ns} =
|
||||
Agent.get_and_update(
|
||||
pid,
|
||||
fn repl_state ->
|
||||
{output, eval_result, new_state} = eval_capturing_output(code, repl_state)
|
||||
ns = CljElixir.REPL.current_ns(new_state)
|
||||
|
||||
{{output, eval_result}, new_state}
|
||||
{{output, eval_result, ns}, new_state}
|
||||
end,
|
||||
:infinity
|
||||
)
|
||||
|
||||
{:reply, {output, result}, state}
|
||||
{:reply, {output, result, ns}, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ defmodule CljElixir.Printer do
|
||||
do_print_str(value)
|
||||
end
|
||||
|
||||
@doc "Clojure-compatible str: nil→\"\", strings pass through, else print representation"
|
||||
def str_value(nil), do: ""
|
||||
def str_value(value), do: print_str(value)
|
||||
|
||||
# Check if IPrintWithWriter is compiled and implemented for this value
|
||||
defp protocol_implemented?(value) do
|
||||
case Code.ensure_loaded(CljElixir.IPrintWithWriter) do
|
||||
|
||||
+168
-22
@@ -4,44 +4,52 @@ defmodule CljElixir.REPL do
|
||||
|
||||
Maintains state across evaluations: bindings persist,
|
||||
modules defined in one evaluation are available in the next.
|
||||
|
||||
Tracks the current namespace (`ns`) so that bare `defn`/`def` forms
|
||||
are merged into the active module and the module is recompiled
|
||||
incrementally.
|
||||
"""
|
||||
|
||||
defstruct bindings: [],
|
||||
history: [],
|
||||
counter: 1,
|
||||
env: nil
|
||||
env: nil,
|
||||
current_ns: nil,
|
||||
module_defs: %{}
|
||||
|
||||
@doc "Create a new REPL state"
|
||||
def new do
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
%__MODULE__{}
|
||||
end
|
||||
|
||||
@doc "Return the current namespace name (defaults to \"user\")"
|
||||
def current_ns(%__MODULE__{current_ns: ns}), do: ns || "user"
|
||||
|
||||
@doc """
|
||||
Evaluate a CljElixir source string in the given REPL state.
|
||||
Returns {:ok, result_string, new_state} or {:error, error_string, new_state}.
|
||||
"""
|
||||
def eval(source, state) do
|
||||
opts = [
|
||||
bindings: state.bindings,
|
||||
file: "repl"
|
||||
]
|
||||
case CljElixir.Reader.read_string(source) do
|
||||
{:ok, forms} ->
|
||||
has_ns = Enum.any?(forms, &ns_form?/1)
|
||||
has_defs = Enum.any?(forms, &def_form?/1)
|
||||
|
||||
case CljElixir.Compiler.eval_string(source, opts) do
|
||||
{:ok, result, new_bindings} ->
|
||||
result_str = CljElixir.Printer.pr_str(result)
|
||||
cond do
|
||||
has_ns ->
|
||||
eval_with_ns(forms, source, state)
|
||||
|
||||
new_state = %{state |
|
||||
bindings: new_bindings,
|
||||
history: [source | state.history],
|
||||
counter: state.counter + 1
|
||||
}
|
||||
has_defs and state.current_ns != nil ->
|
||||
eval_in_ns(forms, source, state)
|
||||
|
||||
{:ok, result_str, new_state}
|
||||
true ->
|
||||
eval_plain(source, state)
|
||||
end
|
||||
|
||||
{:error, errors} ->
|
||||
error_str = format_errors(errors)
|
||||
new_state = %{state | counter: state.counter + 1}
|
||||
{:error, error_str, new_state}
|
||||
{:error, reason} ->
|
||||
error_msg = if is_binary(reason), do: reason, else: inspect(reason)
|
||||
{:error, "Read error: #{error_msg}", %{state | counter: state.counter + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,15 +60,150 @@ defmodule CljElixir.REPL do
|
||||
|> count_delimiters(0, 0, 0, false, false)
|
||||
end
|
||||
|
||||
# Count open/close delimiters, respecting strings and comments
|
||||
# ---------------------------------------------------------------------------
|
||||
# Eval strategies
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Full ns block: set namespace, capture defs, compile normally
|
||||
defp eval_with_ns(forms, source, state) do
|
||||
ns_name = extract_ns_name(forms)
|
||||
new_defs = collect_defs(forms)
|
||||
|
||||
opts = [bindings: state.bindings, file: "repl"]
|
||||
|
||||
case CljElixir.Compiler.eval_string(source, opts) do
|
||||
{:ok, result, new_bindings} ->
|
||||
new_state = %{state |
|
||||
bindings: new_bindings,
|
||||
current_ns: ns_name,
|
||||
module_defs: new_defs,
|
||||
history: [source | state.history],
|
||||
counter: state.counter + 1
|
||||
}
|
||||
|
||||
{:ok, CljElixir.Printer.pr_str(result), new_state}
|
||||
|
||||
{:error, errors} ->
|
||||
{:error, format_errors(errors), %{state | counter: state.counter + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
# Bare defs in active namespace: merge into module_defs and recompile module
|
||||
defp eval_in_ns(forms, source, state) do
|
||||
{new_def_forms, exprs} = Enum.split_with(forms, &def_form?/1)
|
||||
|
||||
# Merge new defs into accumulated module_defs (keyed by name)
|
||||
merged_defs =
|
||||
Enum.reduce(new_def_forms, state.module_defs, fn form, acc ->
|
||||
name = extract_def_name(form)
|
||||
Map.put(acc, name, form)
|
||||
end)
|
||||
|
||||
# Reconstruct: ns + all accumulated defs + current expressions
|
||||
ns_form = make_ns_form(state.current_ns)
|
||||
all_forms = [ns_form | Map.values(merged_defs)] ++ exprs
|
||||
|
||||
opts = [bindings: state.bindings, file: "repl"]
|
||||
|
||||
case CljElixir.Compiler.eval_forms(all_forms, opts) do
|
||||
{:ok, result, new_bindings} ->
|
||||
result_str =
|
||||
if exprs == [] do
|
||||
# Def-only: show var-like representation
|
||||
new_def_forms
|
||||
|> Enum.map(&extract_def_name/1)
|
||||
|> Enum.map_join(" ", &"#'#{state.current_ns}/#{&1}")
|
||||
else
|
||||
CljElixir.Printer.pr_str(result)
|
||||
end
|
||||
|
||||
new_state = %{state |
|
||||
bindings: new_bindings,
|
||||
module_defs: merged_defs,
|
||||
history: [source | state.history],
|
||||
counter: state.counter + 1
|
||||
}
|
||||
|
||||
{:ok, result_str, new_state}
|
||||
|
||||
{:error, errors} ->
|
||||
{:error, format_errors(errors), %{state | counter: state.counter + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
# No ns context: eval as-is (legacy / ad-hoc expressions)
|
||||
defp eval_plain(source, state) do
|
||||
opts = [bindings: state.bindings, file: "repl"]
|
||||
|
||||
case CljElixir.Compiler.eval_string(source, opts) do
|
||||
{:ok, result, new_bindings} ->
|
||||
new_state = %{state |
|
||||
bindings: new_bindings,
|
||||
history: [source | state.history],
|
||||
counter: state.counter + 1
|
||||
}
|
||||
|
||||
{:ok, CljElixir.Printer.pr_str(result), new_state}
|
||||
|
||||
{:error, errors} ->
|
||||
{:error, format_errors(errors), %{state | counter: state.counter + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Form classification helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp ns_form?({:list, _, [{:symbol, _, "ns"} | _]}), do: true
|
||||
defp ns_form?(_), do: false
|
||||
|
||||
defp def_form?({:list, _, [{:symbol, _, name} | _]})
|
||||
when name in ~w(defn defn- def defprotocol defrecord extend-type
|
||||
extend-protocol reify defmacro use),
|
||||
do: true
|
||||
|
||||
defp def_form?({:list, _, [{:symbol, _, "m/=>"} | _]}), do: true
|
||||
defp def_form?(_), do: false
|
||||
|
||||
defp extract_ns_name(forms) do
|
||||
Enum.find_value(forms, fn
|
||||
{:list, _, [{:symbol, _, "ns"}, {:symbol, _, name} | _]} -> name
|
||||
_ -> nil
|
||||
end)
|
||||
end
|
||||
|
||||
defp collect_defs(forms) do
|
||||
forms
|
||||
|> Enum.filter(&def_form?/1)
|
||||
|> Enum.reduce(%{}, fn form, acc ->
|
||||
name = extract_def_name(form)
|
||||
Map.put(acc, name, form)
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_def_name({:list, _, [{:symbol, _, _}, {:symbol, _, name} | _]}), do: name
|
||||
defp extract_def_name(form), do: "anon_#{:erlang.phash2(form)}"
|
||||
|
||||
defp make_ns_form(ns_name) do
|
||||
{:list, %{line: 0, col: 0}, [
|
||||
{:symbol, %{line: 0, col: 0}, "ns"},
|
||||
{:symbol, %{line: 0, col: 0}, ns_name}
|
||||
]}
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Delimiter balancing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp count_delimiters([], parens, brackets, braces, _in_string, _escape) do
|
||||
parens == 0 and brackets == 0 and braces == 0
|
||||
# Negative counts mean excess closing delimiters — let the reader report the error
|
||||
parens < 0 or brackets < 0 or braces < 0 or
|
||||
(parens == 0 and brackets == 0 and braces == 0)
|
||||
end
|
||||
|
||||
defp count_delimiters([char | rest], p, b, br, in_string, escape) do
|
||||
cond do
|
||||
escape ->
|
||||
# Previous char was \, skip this one
|
||||
count_delimiters(rest, p, b, br, in_string, false)
|
||||
|
||||
char == "\\" and in_string ->
|
||||
@@ -76,7 +219,6 @@ defmodule CljElixir.REPL do
|
||||
count_delimiters(rest, p, b, br, true, false)
|
||||
|
||||
char == ";" ->
|
||||
# Comment - skip rest of line
|
||||
rest_after_newline = Enum.drop_while(rest, &(&1 != "\n"))
|
||||
count_delimiters(rest_after_newline, p, b, br, false, false)
|
||||
|
||||
@@ -91,6 +233,10 @@ defmodule CljElixir.REPL do
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Error formatting
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp format_errors(errors) when is_list(errors) do
|
||||
Enum.map_join(errors, "\n", fn
|
||||
%{message: msg, line: line} when is_integer(line) and line > 0 ->
|
||||
|
||||
+119
-33
@@ -53,14 +53,51 @@ defmodule CljElixir.Transformer do
|
||||
def transform(forms, ctx \\ %Context{})
|
||||
|
||||
def transform(forms, ctx) when is_list(forms) do
|
||||
{elixir_forms, _ctx} =
|
||||
Enum.map_reduce(forms, ctx, fn form, acc ->
|
||||
{ast, new_ctx} = transform_form(form, acc)
|
||||
{ast, new_ctx}
|
||||
# Check if file has explicit defmodule forms (ns won't auto-wrap if so)
|
||||
has_defmodule =
|
||||
Enum.any?(forms, fn
|
||||
{:list, _, [{:symbol, _, "defmodule"} | _]} -> true
|
||||
_ -> false
|
||||
end)
|
||||
|
||||
# Filter out nil (from defmacro which produces no runtime code)
|
||||
elixir_forms = Enum.filter(elixir_forms, &(&1 != nil))
|
||||
{elixir_forms, final_ctx} =
|
||||
Enum.map_reduce(forms, ctx, fn form, acc ->
|
||||
{ast, new_ctx} = transform_form(form, acc)
|
||||
# Tag each transformed form with whether the source was a def-like form
|
||||
{{ast, def_form?(form)}, new_ctx}
|
||||
end)
|
||||
|
||||
# Filter out nil (from ns, defmacro which produce no runtime code)
|
||||
elixir_forms = Enum.filter(elixir_forms, fn {ast, _} -> ast != nil end)
|
||||
|
||||
# If ns declared a module and there are no explicit defmodule forms,
|
||||
# separate def-forms (inside module) from expressions (after module)
|
||||
elixir_forms =
|
||||
if final_ctx.module_name != nil and ctx.module_name == nil and not has_defmodule do
|
||||
{defs, exprs} =
|
||||
Enum.split_with(elixir_forms, fn {_ast, is_def} -> is_def end)
|
||||
|
||||
def_asts = Enum.map(defs, fn {ast, _} -> ast end)
|
||||
expr_asts = Enum.map(exprs, fn {ast, _} -> ast end)
|
||||
|
||||
block =
|
||||
case def_asts do
|
||||
[] -> nil
|
||||
[single] -> single
|
||||
multiple -> {:__block__, [], multiple}
|
||||
end
|
||||
|
||||
module_ast =
|
||||
if block do
|
||||
[{:defmodule, [context: Elixir], [final_ctx.module_name, [do: block]]}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
module_ast ++ expr_asts
|
||||
else
|
||||
Enum.map(elixir_forms, fn {ast, _} -> ast end)
|
||||
end
|
||||
|
||||
case elixir_forms do
|
||||
[] -> nil
|
||||
@@ -74,6 +111,30 @@ defmodule CljElixir.Transformer do
|
||||
ast
|
||||
end
|
||||
|
||||
# Transform a list of guard forms into a single ANDed Elixir guard AST.
|
||||
# [:guard [(> x 0) (< x 10)]] → {:and, [], [guard1, guard2]}
|
||||
defp transform_guards(guard_forms, ctx) do
|
||||
guard_asts = Enum.map(guard_forms, &transform(&1, ctx))
|
||||
|
||||
case guard_asts do
|
||||
[single] -> single
|
||||
[first | rest] -> Enum.reduce(rest, first, fn g, acc ->
|
||||
{:and, [context: Elixir], [acc, g]}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
# Is this CljElixir AST form a definition (goes inside defmodule)?
|
||||
defp def_form?({:list, _, [{:symbol, _, name} | _]})
|
||||
when name in ~w(defn defn- def defprotocol defrecord extend-type
|
||||
extend-protocol reify defmacro use),
|
||||
do: true
|
||||
|
||||
# m/=> schema annotations
|
||||
defp def_form?({:list, _, [{:symbol, _, "m/=>"} | _]}), do: true
|
||||
|
||||
defp def_form?(_), do: false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -258,6 +319,7 @@ defmodule CljElixir.Transformer do
|
||||
defp transform_list([head | args], meta, ctx) do
|
||||
case head do
|
||||
# --- Special forms (symbols) ---
|
||||
{:symbol, _, "ns"} -> transform_ns(args, meta, ctx)
|
||||
{:symbol, _, "defmodule"} -> transform_defmodule(args, meta, ctx)
|
||||
{:symbol, _, "defn"} -> transform_defn(args, meta, ctx, :def)
|
||||
{:symbol, _, "defn-"} -> transform_defn(args, meta, ctx, :defp)
|
||||
@@ -419,6 +481,17 @@ defmodule CljElixir.Transformer do
|
||||
end
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 0. ns — module declaration (sets ctx.module_name for auto-wrapping)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp transform_ns([name_form | _rest], _meta, ctx) do
|
||||
mod_alias = module_name_ast(name_form)
|
||||
{nil, %{ctx | module_name: mod_alias}}
|
||||
end
|
||||
|
||||
defp transform_ns([], _meta, ctx), do: {nil, ctx}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. defmodule
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -527,8 +600,8 @@ defmodule CljElixir.Transformer do
|
||||
nil ->
|
||||
{def_kind, em, [call_with_args(fun_name, param_asts), [do: body_ast]]}
|
||||
|
||||
guard_form ->
|
||||
guard_ast = transform(guard_form, fn_ctx)
|
||||
guard_forms ->
|
||||
guard_ast = transform_guards(guard_forms, fn_ctx)
|
||||
|
||||
{def_kind, em,
|
||||
[
|
||||
@@ -568,18 +641,18 @@ defmodule CljElixir.Transformer do
|
||||
{required, rest_param, nil, body}
|
||||
|
||||
{:list, _, clause_elements} ->
|
||||
# Might have guard: ([params] :when guard body)
|
||||
parse_clause_with_guard(clause_elements)
|
||||
# Might have guard: ([params] :guard guard body)
|
||||
parse_clause_with_guards(clause_elements)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_clause_with_guard([{:vector, _, params}, :when, guard | body]) do
|
||||
defp parse_clause_with_guards([{:vector, _, params}, :guard, {:vector, _, guards} | body]) do
|
||||
{required, rest_param} = split_rest_params(params)
|
||||
{required, rest_param, guard, body}
|
||||
{required, rest_param, guards, body}
|
||||
end
|
||||
|
||||
defp parse_clause_with_guard([{:vector, _, params} | body]) do
|
||||
defp parse_clause_with_guards([{:vector, _, params} | body]) do
|
||||
{required, rest_param} = split_rest_params(params)
|
||||
{required, rest_param, nil, body}
|
||||
end
|
||||
@@ -630,8 +703,8 @@ defmodule CljElixir.Transformer do
|
||||
nil ->
|
||||
{:->, [], [all_param_asts, body_ast]}
|
||||
|
||||
guard_form ->
|
||||
guard_ast = transform(guard_form, ctx)
|
||||
guard_forms ->
|
||||
guard_ast = transform_guards(guard_forms, ctx)
|
||||
guard_params = [{:when, [], all_param_asts ++ [guard_ast]}]
|
||||
{:->, [], [guard_params, body_ast]}
|
||||
end
|
||||
@@ -657,7 +730,7 @@ defmodule CljElixir.Transformer do
|
||||
{required, rest_param, nil, body}
|
||||
|
||||
{:list, _, clause_elements} ->
|
||||
parse_clause_with_guard(clause_elements)
|
||||
parse_clause_with_guards(clause_elements)
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -823,8 +896,8 @@ defmodule CljElixir.Transformer do
|
||||
clauses
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.map(fn
|
||||
[pattern, :when | rest] ->
|
||||
# pattern :when guard body — need to re-chunk
|
||||
[pattern, :guard | rest] ->
|
||||
# pattern :guard guard body — need to re-chunk
|
||||
# This won't happen with chunk_every(2), handle differently
|
||||
pat_ast = transform(pattern, pattern_ctx)
|
||||
body_ast = transform(List.last(rest), ctx)
|
||||
@@ -1550,8 +1623,8 @@ defmodule CljElixir.Transformer do
|
||||
nil ->
|
||||
{:->, [], [[pat_ast], body_ast]}
|
||||
|
||||
guard_form ->
|
||||
guard_ast = transform(guard_form, ctx)
|
||||
guard_forms ->
|
||||
guard_ast = transform_guards(guard_forms, ctx)
|
||||
{:->, [], [[{:when, [], [pat_ast, guard_ast]}], body_ast]}
|
||||
end
|
||||
end)
|
||||
@@ -1585,8 +1658,8 @@ defmodule CljElixir.Transformer do
|
||||
parse_receive_clauses(rest, acc, {timeout, body})
|
||||
end
|
||||
|
||||
defp parse_receive_clauses([pattern, :when, guard, body | rest], acc, after_clause) do
|
||||
parse_receive_clauses(rest, [{pattern, guard, body} | acc], after_clause)
|
||||
defp parse_receive_clauses([pattern, :guard, {:vector, _, guards}, body | rest], acc, after_clause) do
|
||||
parse_receive_clauses(rest, [{pattern, guards, body} | acc], after_clause)
|
||||
end
|
||||
|
||||
defp parse_receive_clauses([pattern, body | rest], acc, after_clause) do
|
||||
@@ -1959,23 +2032,24 @@ defmodule CljElixir.Transformer do
|
||||
{{:-, [], [a_ast, 1]}, ctx}
|
||||
end
|
||||
|
||||
# str — concatenate with <> using to_string
|
||||
# str — Clojure-compatible: nil→"", strings pass through, collections use print repr
|
||||
defp transform_str(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
|
||||
str_call = fn arg ->
|
||||
{{:., [], [{:__aliases__, [alias: false], [:CljElixir, :Printer]}, :str_value]}, [], [arg]}
|
||||
end
|
||||
|
||||
ast =
|
||||
case t_args do
|
||||
[] ->
|
||||
""
|
||||
|
||||
[single] ->
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Kernel]}, :to_string]}, [], [single]}
|
||||
str_call.(single)
|
||||
|
||||
_ ->
|
||||
stringified =
|
||||
Enum.map(t_args, fn a ->
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Kernel]}, :to_string]}, [], [a]}
|
||||
end)
|
||||
stringified = Enum.map(t_args, str_call)
|
||||
|
||||
Enum.reduce(tl(stringified), hd(stringified), fn arg, acc ->
|
||||
{:<>, [], [acc, arg]}
|
||||
@@ -1985,12 +2059,12 @@ defmodule CljElixir.Transformer do
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# println → IO.puts
|
||||
# println → IO.puts, returns nil (Clojure convention)
|
||||
defp transform_println(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
|
||||
# If multiple args, join with str first
|
||||
ast =
|
||||
io_call =
|
||||
case t_args do
|
||||
[single] ->
|
||||
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], [single]}
|
||||
@@ -2001,6 +2075,7 @@ defmodule CljElixir.Transformer do
|
||||
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], [str_ast]}
|
||||
end
|
||||
|
||||
ast = {:__block__, [], [io_call, nil]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -2052,11 +2127,11 @@ defmodule CljElixir.Transformer do
|
||||
end
|
||||
end)
|
||||
|
||||
ast = {:__block__, [], writes}
|
||||
ast = {:__block__, [], writes ++ [nil]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# (prn val) -> IO.puts(CljElixir.Printer.pr_str(val))
|
||||
# (prn val) -> IO.puts(CljElixir.Printer.pr_str(val)), returns nil
|
||||
# Multiple args joined with spaces, then newline
|
||||
defp transform_prn(args, ctx) do
|
||||
t_args = Enum.map(args, fn a -> transform(a, ctx) end)
|
||||
@@ -2079,7 +2154,8 @@ defmodule CljElixir.Transformer do
|
||||
end)
|
||||
end
|
||||
|
||||
ast = {{:., [], [io_mod, :puts]}, [], [joined]}
|
||||
io_call = {{:., [], [io_mod, :puts]}, [], [joined]}
|
||||
ast = {:__block__, [], [io_call, nil]}
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
@@ -2535,6 +2611,16 @@ defmodule CljElixir.Transformer do
|
||||
{ast, ctx}
|
||||
end
|
||||
|
||||
# update - (update m k f x y ...) => rewrite to (assoc m k (f (get m k) x y ...))
|
||||
# Rewritten at AST level so f goes through builtin dispatch (e.g. dissoc)
|
||||
defp transform_update([m, k, f | extra_args], ctx) do
|
||||
meta = %{line: 0, col: 0}
|
||||
get_call = {:list, meta, [{:symbol, meta, "get"}, m, k]}
|
||||
f_call = {:list, meta, [f, get_call | extra_args]}
|
||||
assoc_call = {:list, meta, [{:symbol, meta, "assoc"}, m, k, f_call]}
|
||||
do_transform(assoc_call, ctx)
|
||||
end
|
||||
|
||||
# conj
|
||||
defp transform_conj([c, x], ctx) do
|
||||
c_ast = transform(c, ctx)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
defmodule Mix.Tasks.Clje.Build do
|
||||
@moduledoc """
|
||||
Compile CljElixir files to BEAM bytecode.
|
||||
|
||||
## Usage
|
||||
|
||||
mix clje.build src/my_module.clje
|
||||
mix clje.build src/foo.clje src/bar.clje
|
||||
mix clje.build src/foo.clje -o _build/dev/lib/clj_elixir/ebin
|
||||
|
||||
Like `elixirc`. Compiles `.clje` files to `.beam` files without running them.
|
||||
|
||||
## Options
|
||||
|
||||
* `-o` / `--output` - output directory for .beam files (default: `_build/dev/lib/<app>/ebin`)
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Compile .clje files to BEAM bytecode"
|
||||
|
||||
@impl Mix.Task
|
||||
def run(args) do
|
||||
{opts, files, _} =
|
||||
OptionParser.parse(args,
|
||||
switches: [output: :string],
|
||||
aliases: [o: :output]
|
||||
)
|
||||
|
||||
if files == [] do
|
||||
Mix.shell().error("Usage: mix clje.build <file.clje> [...] [-o output_dir]")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
Mix.Task.run("compile")
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
output_dir = opts[:output] || Mix.Project.compile_path()
|
||||
|
||||
results =
|
||||
Enum.map(files, fn file ->
|
||||
Mix.shell().info("Compiling #{file}")
|
||||
|
||||
case CljElixir.Compiler.compile_file_to_beam(file, output_dir: output_dir) do
|
||||
{:ok, modules} ->
|
||||
Enum.each(modules, fn {mod, _binary} ->
|
||||
Mix.shell().info(" -> #{mod}")
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, diagnostics} ->
|
||||
Enum.each(diagnostics, fn diag ->
|
||||
loc =
|
||||
case {Map.get(diag, :file), Map.get(diag, :line, 0)} do
|
||||
{nil, _} -> ""
|
||||
{_f, 0} -> "#{diag.file}: "
|
||||
{_f, l} -> "#{diag.file}:#{l}: "
|
||||
end
|
||||
|
||||
Mix.shell().error("#{loc}#{diag.severity}: #{diag.message}")
|
||||
end)
|
||||
|
||||
:error
|
||||
end
|
||||
end)
|
||||
|
||||
if Enum.any?(results, &(&1 == :error)) do
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
defmodule Mix.Tasks.Clje.Eval do
|
||||
@moduledoc """
|
||||
Evaluate a CljElixir expression from the command line.
|
||||
|
||||
## Usage
|
||||
|
||||
mix clje.eval '(+ 1 2)'
|
||||
mix clje.eval '(defn greet [name] (str "hello " name))' '(greet "world")'
|
||||
|
||||
Multiple expressions are evaluated in sequence, with bindings persisting.
|
||||
The result of the last expression is printed.
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Evaluate CljElixir expressions"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([]) do
|
||||
Mix.shell().error("Usage: mix clje.eval '<expression>' [...]")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
def run(exprs) do
|
||||
Mix.Task.run("compile")
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
{result, _bindings} =
|
||||
Enum.reduce(exprs, {nil, []}, fn expr, {_prev, bindings} ->
|
||||
case CljElixir.Compiler.eval_string(expr, bindings: bindings) do
|
||||
{:ok, result, new_bindings} ->
|
||||
{result, new_bindings}
|
||||
|
||||
{:error, diagnostics} ->
|
||||
Enum.each(diagnostics, fn diag ->
|
||||
Mix.shell().error("#{diag.severity}: #{diag.message}")
|
||||
end)
|
||||
|
||||
System.halt(1)
|
||||
end
|
||||
end)
|
||||
|
||||
IO.puts(CljElixir.Printer.pr_str(result))
|
||||
end
|
||||
end
|
||||
+40
-13
@@ -38,7 +38,8 @@ defmodule Mix.Tasks.Clje.Repl do
|
||||
end
|
||||
|
||||
defp loop(state) do
|
||||
prompt = "clje:#{state.counter}> "
|
||||
ns = CljElixir.REPL.current_ns(state)
|
||||
prompt = "#{ns}:#{state.counter}> "
|
||||
|
||||
case read_input(prompt) do
|
||||
:eof ->
|
||||
@@ -80,10 +81,28 @@ defmodule Mix.Tasks.Clje.Repl do
|
||||
end
|
||||
|
||||
defp read_input(prompt) do
|
||||
case IO.gets(prompt) do
|
||||
:eof -> :eof
|
||||
{:error, _} -> :eof
|
||||
data -> data
|
||||
IO.write(prompt)
|
||||
read_line()
|
||||
end
|
||||
|
||||
# Read a line character-by-character, treating both \r and \n as line terminators.
|
||||
# This avoids IO.gets hanging when the terminal sends \r without \n.
|
||||
defp read_line, do: read_line([])
|
||||
|
||||
defp read_line(acc) do
|
||||
case IO.getn("", 1) do
|
||||
:eof ->
|
||||
if acc == [], do: :eof, else: acc |> Enum.reverse() |> IO.iodata_to_binary()
|
||||
|
||||
{:error, _} ->
|
||||
if acc == [], do: :eof, else: acc |> Enum.reverse() |> IO.iodata_to_binary()
|
||||
|
||||
<<c>> when c in [?\r, ?\n] ->
|
||||
IO.write("\n")
|
||||
acc |> Enum.reverse() |> IO.iodata_to_binary()
|
||||
|
||||
char ->
|
||||
read_line([char | acc])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,16 +115,24 @@ defmodule Mix.Tasks.Clje.Repl do
|
||||
end
|
||||
|
||||
defp read_continuation(acc) do
|
||||
case IO.gets(" ") do
|
||||
:eof -> acc
|
||||
{:error, _} -> acc
|
||||
line ->
|
||||
new_acc = acc <> "\n" <> String.trim_trailing(line, "\n")
|
||||
IO.write(" ")
|
||||
|
||||
if CljElixir.REPL.balanced?(new_acc) do
|
||||
new_acc
|
||||
case read_line() do
|
||||
:eof -> acc
|
||||
line ->
|
||||
trimmed = String.trim(line)
|
||||
|
||||
if trimmed == "" do
|
||||
# Empty Enter in continuation mode: submit what we have
|
||||
acc
|
||||
else
|
||||
read_continuation(new_acc)
|
||||
new_acc = acc <> "\n" <> trimmed
|
||||
|
||||
if CljElixir.REPL.balanced?(new_acc) do
|
||||
new_acc
|
||||
else
|
||||
read_continuation(new_acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
defmodule Mix.Tasks.Clje.Run do
|
||||
@moduledoc """
|
||||
Compile and run a CljElixir file.
|
||||
|
||||
## Usage
|
||||
|
||||
mix clje.run examples/chat_room.clje
|
||||
mix clje.run -e '(println "hello")' script.clje
|
||||
|
||||
Like `elixir script.exs` or `bb script.clj`. The file is compiled and
|
||||
evaluated. Modules defined in the file become available.
|
||||
|
||||
## Options
|
||||
|
||||
* `-e` / `--eval` - evaluate expression before running the file
|
||||
* `--no-halt` - keep the system running after execution (useful for spawned processes)
|
||||
|
||||
Arguments after `--` are available via `System.argv()`.
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Run a CljElixir file"
|
||||
|
||||
@impl Mix.Task
|
||||
def run(args) do
|
||||
# Split on "--" to separate mix opts from script args
|
||||
{before_dashdash, script_args} = split_on_dashdash(args)
|
||||
|
||||
{opts, positional, _} =
|
||||
OptionParser.parse(before_dashdash,
|
||||
switches: [eval: :keep, no_halt: :boolean],
|
||||
aliases: [e: :eval]
|
||||
)
|
||||
|
||||
Mix.Task.run("compile")
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
# Make script args available via System.argv()
|
||||
System.argv(script_args)
|
||||
|
||||
# Evaluate any -e expressions first
|
||||
bindings =
|
||||
opts
|
||||
|> Keyword.get_values(:eval)
|
||||
|> Enum.reduce([], fn expr, bindings ->
|
||||
case CljElixir.Compiler.eval_string(expr, bindings: bindings) do
|
||||
{:ok, result, new_bindings} ->
|
||||
IO.puts(CljElixir.Printer.pr_str(result))
|
||||
new_bindings
|
||||
|
||||
{:error, diagnostics} ->
|
||||
print_diagnostics(diagnostics)
|
||||
System.halt(1)
|
||||
end
|
||||
end)
|
||||
|
||||
# Run file(s)
|
||||
case positional do
|
||||
[] ->
|
||||
unless Keyword.has_key?(opts, :eval) do
|
||||
Mix.shell().error("Usage: mix clje.run [options] <file.clje>")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
files ->
|
||||
Enum.reduce(files, bindings, fn file, bindings ->
|
||||
case CljElixir.Compiler.eval_file(file, bindings: bindings) do
|
||||
{:ok, _result, new_bindings} ->
|
||||
new_bindings
|
||||
|
||||
{:error, diagnostics} ->
|
||||
print_diagnostics(diagnostics)
|
||||
System.halt(1)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if opts[:no_halt] do
|
||||
Process.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
defp split_on_dashdash(args) do
|
||||
case Enum.split_while(args, &(&1 != "--")) do
|
||||
{before, ["--" | rest]} -> {before, rest}
|
||||
{before, []} -> {before, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp print_diagnostics(diagnostics) do
|
||||
Enum.each(diagnostics, fn diag ->
|
||||
loc =
|
||||
case {Map.get(diag, :file), Map.get(diag, :line, 0)} do
|
||||
{nil, _} -> ""
|
||||
{_f, 0} -> "#{diag.file}: "
|
||||
{_f, l} -> "#{diag.file}:#{l}: "
|
||||
end
|
||||
|
||||
Mix.shell().error("#{loc}#{diag.severity}: #{diag.message}")
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
(ns Access
|
||||
"Elixir Access module — nested data access and update.
|
||||
|
||||
In CljElixir: Provides access functions used with get-in, update-in, etc.
|
||||
Access functions create composable paths for nested data manipulation.")
|
||||
|
||||
(defn key
|
||||
"Accesses a key in a map. Returns `default` if key is missing.
|
||||
(get-in data [(Access/key :user) (Access/key :name)])"
|
||||
([key])
|
||||
([key default]))
|
||||
|
||||
(defn key!
|
||||
"Accesses a key in a map. Raises if key is missing."
|
||||
[key])
|
||||
|
||||
(defn elem
|
||||
"Accesses an element in a tuple by index.
|
||||
(get-in data [(Access/elem 0)])"
|
||||
[index])
|
||||
|
||||
(defn at
|
||||
"Accesses an element in a list by index.
|
||||
(get-in data [(Access/at 0)])"
|
||||
([index])
|
||||
([index default]))
|
||||
|
||||
(defn at!
|
||||
"Accesses a list element by index. Raises on out of bounds."
|
||||
[index])
|
||||
|
||||
(defn all
|
||||
"Accesses all elements in a list.
|
||||
(get-in data [(Access/all)])"
|
||||
[])
|
||||
|
||||
(defn filter
|
||||
"Filters elements in a list.
|
||||
(update-in data [(Access/filter (fn [x] (> x 2)))] inc)"
|
||||
[fun])
|
||||
|
||||
(defn find
|
||||
"Finds the first element matching `fun`.
|
||||
(get-in data [(Access/find (fn [x] (> x 2)))])"
|
||||
[fun])
|
||||
|
||||
(defn slice
|
||||
"Accesses a slice of a list.
|
||||
(get-in data [(Access/slice 1 3)])"
|
||||
[range-or-index])
|
||||
|
||||
(defn pop
|
||||
"Pops a key from a map or index from a list."
|
||||
[key])
|
||||
|
||||
(defn fetch
|
||||
"Fetches a value with {:ok value} or :error semantics."
|
||||
[container key])
|
||||
|
||||
(defn fetch!
|
||||
"Fetches a value. Raises on missing key."
|
||||
[container key])
|
||||
|
||||
(defn get
|
||||
"Gets a value from a container with optional default."
|
||||
([container key])
|
||||
([container key default]))
|
||||
|
||||
(defn get-and-update
|
||||
"Gets and updates a key in one operation."
|
||||
[container key fun])
|
||||
|
||||
(defn get-and-update!
|
||||
"Gets and updates. Raises if container doesn't implement Access."
|
||||
[container key fun])
|
||||
@@ -0,0 +1,60 @@
|
||||
(ns Agent
|
||||
"Elixir Agent module — simple state wrapper around a process.
|
||||
|
||||
In CljElixir: (Agent/start-link (fn [] initial-state)), etc.
|
||||
Agents are a simpler alternative to GenServer for pure state management.")
|
||||
|
||||
(defn start
|
||||
"Starts an Agent without linking. Returns {:ok pid}.
|
||||
(Agent/start (fn [] {:count 0}))"
|
||||
([fun])
|
||||
([fun opts])
|
||||
([module fun args])
|
||||
([module fun args opts]))
|
||||
|
||||
(defn start-link
|
||||
"Starts an Agent linked to the current process. Returns {:ok pid}.
|
||||
(Agent/start-link (fn [] {:count 0}))
|
||||
(Agent/start-link (fn [] {:count 0}) :name :my-agent)"
|
||||
([fun])
|
||||
([fun opts])
|
||||
([module fun args])
|
||||
([module fun args opts]))
|
||||
|
||||
(defn get
|
||||
"Gets the agent state by applying `fun`. Blocks until result.
|
||||
(Agent/get pid (fn [state] (:count state))) ;=> 0
|
||||
(Agent/get pid (fn [state] state) 5000) ;=> with 5s timeout"
|
||||
([agent fun])
|
||||
([agent fun timeout])
|
||||
([agent module fun args])
|
||||
([agent module fun args timeout]))
|
||||
|
||||
(defn update
|
||||
"Updates the agent state by applying `fun`. Returns :ok.
|
||||
(Agent/update pid (fn [state] (update state :count inc))) ;=> :ok"
|
||||
([agent fun])
|
||||
([agent fun timeout])
|
||||
([agent module fun args])
|
||||
([agent module fun args timeout]))
|
||||
|
||||
(defn get-and-update
|
||||
"Gets and updates in one operation. `fun` returns {get_value, new_state}.
|
||||
(Agent/get-and-update pid (fn [state] {(:count state) (update state :count inc)}))"
|
||||
([agent fun])
|
||||
([agent fun timeout])
|
||||
([agent module fun args])
|
||||
([agent module fun args timeout]))
|
||||
|
||||
(defn cast
|
||||
"Async update. Returns :ok immediately.
|
||||
(Agent/cast pid (fn [state] (assoc state :key \"val\"))) ;=> :ok"
|
||||
([agent fun])
|
||||
([agent module fun args]))
|
||||
|
||||
(defn stop
|
||||
"Stops the agent.
|
||||
(Agent/stop pid) ;=> :ok"
|
||||
([agent])
|
||||
([agent reason])
|
||||
([agent reason timeout]))
|
||||
@@ -0,0 +1,104 @@
|
||||
(ns Application
|
||||
"Elixir Application module — OTP application management.
|
||||
|
||||
In CljElixir: (Application/get-env :my-app :key), etc.
|
||||
Applications are the unit of deployment on the BEAM.")
|
||||
|
||||
(defn get-env
|
||||
"Gets application environment value.
|
||||
(Application/get-env :my-app :key) ;=> value or nil
|
||||
(Application/get-env :my-app :key :default) ;=> value or :default"
|
||||
([app key])
|
||||
([app key default]))
|
||||
|
||||
(defn fetch-env
|
||||
"Gets app env. Returns {:ok value} or :error.
|
||||
(Application/fetch-env :my-app :key) ;=> {:ok value}"
|
||||
[app key])
|
||||
|
||||
(defn fetch-env!
|
||||
"Gets app env. Raises if missing."
|
||||
[app key])
|
||||
|
||||
(defn get-all-env
|
||||
"Returns all environment for an application.
|
||||
(Application/get-all-env :my-app) ;=> [[:key1 val1] [:key2 val2]]"
|
||||
[app])
|
||||
|
||||
(defn put-env
|
||||
"Sets an application environment value.
|
||||
(Application/put-env :my-app :key \"value\")"
|
||||
([app key value])
|
||||
([app key value opts]))
|
||||
|
||||
(defn put-all-env
|
||||
"Sets multiple app env values."
|
||||
([app config])
|
||||
([app config opts]))
|
||||
|
||||
(defn delete-env
|
||||
"Deletes an application environment value."
|
||||
[app key])
|
||||
|
||||
(defn start
|
||||
"Starts an application and its dependencies.
|
||||
(Application/start :my-app) ;=> :ok
|
||||
(Application/start :my-app :permanent) ;=> with restart type"
|
||||
([app])
|
||||
([app type]))
|
||||
|
||||
(defn stop
|
||||
"Stops an application.
|
||||
(Application/stop :my-app) ;=> :ok"
|
||||
[app])
|
||||
|
||||
(defn ensure-started
|
||||
"Ensures an application is started. No-op if already running.
|
||||
(Application/ensure-started :logger) ;=> :ok"
|
||||
([app])
|
||||
([app type]))
|
||||
|
||||
(defn ensure-all-started
|
||||
"Starts an application and all dependencies.
|
||||
(Application/ensure-all-started :my-app) ;=> {:ok [:dep1 :dep2 :my-app]}"
|
||||
([app])
|
||||
([app type]))
|
||||
|
||||
(defn started-applications
|
||||
"Returns a list of all started applications.
|
||||
(Application/started-applications) ;=> [[:kernel \"...\" '1.0'] ...]"
|
||||
([])
|
||||
([timeout]))
|
||||
|
||||
(defn loaded-applications
|
||||
"Returns a list of all loaded applications."
|
||||
([])
|
||||
([timeout]))
|
||||
|
||||
(defn which-applications
|
||||
"Returns running applications.
|
||||
(Application/which-applications) ;=> [...]"
|
||||
([])
|
||||
([timeout]))
|
||||
|
||||
(defn spec
|
||||
"Returns the application specification.
|
||||
(Application/spec :my-app) ;=> full spec
|
||||
(Application/spec :my-app :vsn) ;=> version"
|
||||
([app])
|
||||
([app key]))
|
||||
|
||||
(defn app-dir
|
||||
"Returns the application directory.
|
||||
(Application/app-dir :my-app) ;=> \"/path/to/my_app\""
|
||||
([app])
|
||||
([app path]))
|
||||
|
||||
(defn compile-env
|
||||
"Reads compile-time environment values."
|
||||
([app key])
|
||||
([app key default]))
|
||||
|
||||
(defn compile-env!
|
||||
"Reads compile-time environment. Raises if missing."
|
||||
[app key])
|
||||
@@ -0,0 +1,16 @@
|
||||
(ns Atom
|
||||
"Elixir Atom module — atom operations.
|
||||
|
||||
In CljElixir: (Atom/to-string :hello), etc.
|
||||
Atoms are constants whose value is their name.")
|
||||
|
||||
(defn to-string
|
||||
"Converts an atom to a string.
|
||||
(Atom/to-string :hello) ;=> \"hello\"
|
||||
(Atom/to-string :Elixir.MyModule) ;=> \"Elixir.MyModule\""
|
||||
[atom])
|
||||
|
||||
(defn to-charlist
|
||||
"Converts an atom to a charlist.
|
||||
(Atom/to-charlist :hello) ;=> 'hello'"
|
||||
[atom])
|
||||
@@ -0,0 +1,88 @@
|
||||
(ns Base
|
||||
"Elixir Base module — encoding/decoding (base16, base32, base64).
|
||||
|
||||
In CljElixir: (Base/encode64 data), (Base/decode16 hex-string), etc.")
|
||||
|
||||
(defn encode16
|
||||
"Encodes binary to base-16 (hex) string.
|
||||
(Base/encode16 \"hello\") ;=> \"68656C6C6F\"
|
||||
(Base/encode16 \"hello\" :case :lower) ;=> \"68656c6c6f\""
|
||||
([data])
|
||||
([data opts]))
|
||||
|
||||
(defn decode16
|
||||
"Decodes a base-16 string. Returns {:ok binary} or :error.
|
||||
(Base/decode16 \"68656C6C6F\") ;=> {:ok \"hello\"}"
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn decode16!
|
||||
"Decodes base-16. Raises on error.
|
||||
(Base/decode16! \"68656C6C6F\") ;=> \"hello\""
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn encode32
|
||||
"Encodes binary to base-32 string.
|
||||
(Base/encode32 \"hello\") ;=> \"NBSWY3DP\""
|
||||
([data])
|
||||
([data opts]))
|
||||
|
||||
(defn decode32
|
||||
"Decodes a base-32 string. Returns {:ok binary} or :error."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn decode32!
|
||||
"Decodes base-32. Raises on error."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn encode64
|
||||
"Encodes binary to base-64 string.
|
||||
(Base/encode64 \"hello\") ;=> \"aGVsbG8=\""
|
||||
([data])
|
||||
([data opts]))
|
||||
|
||||
(defn decode64
|
||||
"Decodes a base-64 string. Returns {:ok binary} or :error.
|
||||
(Base/decode64 \"aGVsbG8=\") ;=> {:ok \"hello\"}"
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn decode64!
|
||||
"Decodes base-64. Raises on error.
|
||||
(Base/decode64! \"aGVsbG8=\") ;=> \"hello\""
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn url-encode64
|
||||
"Encodes binary to URL-safe base-64.
|
||||
(Base/url-encode64 data) ;=> URL-safe base64 string"
|
||||
([data])
|
||||
([data opts]))
|
||||
|
||||
(defn url-decode64
|
||||
"Decodes URL-safe base-64. Returns {:ok binary} or :error."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn url-decode64!
|
||||
"Decodes URL-safe base-64. Raises on error."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn hex-encode32
|
||||
"Encodes binary to hex-base-32 (Extended Hex)."
|
||||
([data])
|
||||
([data opts]))
|
||||
|
||||
(defn hex-decode32
|
||||
"Decodes hex-base-32."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn hex-decode32!
|
||||
"Decodes hex-base-32. Raises on error."
|
||||
([string])
|
||||
([string opts]))
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
(ns Code
|
||||
"Elixir Code module — code loading, compilation, and evaluation.
|
||||
|
||||
In CljElixir: (Code/eval-string \"1 + 2\"), (Code/compile-file \"mod.ex\"), etc.")
|
||||
|
||||
(defn eval-string
|
||||
"Evaluates Elixir code from a string. Returns {result, binding}.
|
||||
(Code/eval-string \"1 + 2\") ;=> {3 []}"
|
||||
([string])
|
||||
([string binding])
|
||||
([string binding opts]))
|
||||
|
||||
(defn eval-quoted
|
||||
"Evaluates a quoted Elixir AST.
|
||||
(Code/eval-quoted ast binding)"
|
||||
([quoted])
|
||||
([quoted binding])
|
||||
([quoted binding opts]))
|
||||
|
||||
(defn compile-string
|
||||
"Compiles Elixir code from a string."
|
||||
([string])
|
||||
([string file]))
|
||||
|
||||
(defn compile-file
|
||||
"Compiles an Elixir source file."
|
||||
([file])
|
||||
([file relative-to]))
|
||||
|
||||
(defn compile-quoted
|
||||
"Compiles a quoted AST."
|
||||
([quoted])
|
||||
([quoted file]))
|
||||
|
||||
(defn require-file
|
||||
"Requires a file, compiling it if needed. No-op if already loaded."
|
||||
([file])
|
||||
([file relative-to]))
|
||||
|
||||
(defn ensure-loaded
|
||||
"Ensures the module is loaded. Returns {:module module} or {:error reason}.
|
||||
(Code/ensure-loaded Enum) ;=> {:module Enum}"
|
||||
[module])
|
||||
|
||||
(defn ensure-loaded!
|
||||
"Ensures the module is loaded. Raises on error."
|
||||
[module])
|
||||
|
||||
(defn ensure-compiled
|
||||
"Ensures the module is compiled. Returns {:module module} or {:error reason}."
|
||||
[module])
|
||||
|
||||
(defn ensure-compiled!
|
||||
"Ensures the module is compiled. Raises on error."
|
||||
[module])
|
||||
|
||||
(defn loaded?
|
||||
"Returns true if the module is loaded.
|
||||
(Code/loaded? Enum) ;=> true"
|
||||
[module])
|
||||
|
||||
(defn available?
|
||||
"Returns true if the module is available."
|
||||
[module])
|
||||
|
||||
(defn fetch-docs
|
||||
"Returns documentation for a module."
|
||||
[module])
|
||||
|
||||
(defn get-docs
|
||||
"Returns docs for a module, function, or callback."
|
||||
([module spec]))
|
||||
|
||||
(defn string-to-quoted
|
||||
"Parses an Elixir string to AST. Returns {:ok ast} or {:error reason}.
|
||||
(Code/string-to-quoted \"1 + 2\") ;=> {:ok {:+ [line: 1] [1 2]}}"
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn string-to-quoted!
|
||||
"Parses to AST. Raises on error."
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn quoted-to-algebra
|
||||
"Converts quoted AST to formatted algebra document."
|
||||
([quoted])
|
||||
([quoted opts]))
|
||||
|
||||
(defn format-string!
|
||||
"Formats Elixir code string.
|
||||
(Code/format-string! \"1+2\") ;=> \"1 + 2\""
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn format-file!
|
||||
"Formats an Elixir source file."
|
||||
([file])
|
||||
([file opts]))
|
||||
|
||||
(defn purge
|
||||
"Purges a module (removes old code)."
|
||||
[module])
|
||||
|
||||
(defn delete
|
||||
"Deletes a module from the VM."
|
||||
[module])
|
||||
|
||||
(defn compiler-options
|
||||
"Gets or sets compiler options.
|
||||
(Code/compiler-options) ;=> current options"
|
||||
([])
|
||||
([opts]))
|
||||
|
||||
(defn put-compiler-option
|
||||
"Sets a single compiler option."
|
||||
[key value])
|
||||
|
||||
(defn unrequire-files
|
||||
"Un-requires files so they can be required again."
|
||||
[files])
|
||||
|
||||
(defn required-files
|
||||
"Returns list of required files."
|
||||
[])
|
||||
|
||||
(defn append-path
|
||||
"Appends a path to the code path."
|
||||
[path])
|
||||
|
||||
(defn prepend-path
|
||||
"Prepends a path to the code path."
|
||||
[path])
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
(ns Date
|
||||
"Elixir Date module — calendar date operations.
|
||||
|
||||
In CljElixir: (Date/utc-today), (Date/new 2024 3 9), etc.")
|
||||
|
||||
(defn utc-today
|
||||
"Returns today's date in UTC.
|
||||
(Date/utc-today) ;=> ~D[2024-03-09]"
|
||||
([])
|
||||
([calendar]))
|
||||
|
||||
(defn new
|
||||
"Creates a new date.
|
||||
(Date/new 2024 3 9) ;=> {:ok ~D[2024-03-09]}"
|
||||
([year month day])
|
||||
([year month day calendar]))
|
||||
|
||||
(defn new!
|
||||
"Creates a new date. Raises on error.
|
||||
(Date/new! 2024 3 9) ;=> ~D[2024-03-09]"
|
||||
([year month day])
|
||||
([year month day calendar]))
|
||||
|
||||
(defn from-iso8601
|
||||
"Parses ISO 8601 date. Returns {:ok date} or {:error reason}.
|
||||
(Date/from-iso8601 \"2024-03-09\") ;=> {:ok ~D[2024-03-09]}"
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn from-iso8601!
|
||||
"Parses ISO 8601. Raises on error."
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn to-iso8601
|
||||
"Converts to ISO 8601 string.
|
||||
(Date/to-iso8601 date) ;=> \"2024-03-09\""
|
||||
([date])
|
||||
([date format]))
|
||||
|
||||
(defn to-string
|
||||
"Converts to string.
|
||||
(Date/to-string date) ;=> \"2024-03-09\""
|
||||
[date])
|
||||
|
||||
(defn add
|
||||
"Adds days to a date.
|
||||
(Date/add date 7) ;=> one week later
|
||||
(Date/add date 1 :month)"
|
||||
([date days])
|
||||
([date amount unit]))
|
||||
|
||||
(defn diff
|
||||
"Returns the difference in days between two dates.
|
||||
(Date/diff date1 date2) ;=> 7"
|
||||
[date1 date2])
|
||||
|
||||
(defn day-of-week
|
||||
"Returns the day of the week (1=Monday, 7=Sunday).
|
||||
(Date/day-of-week date) ;=> 6 (Saturday)"
|
||||
([date])
|
||||
([date starting-on]))
|
||||
|
||||
(defn day-of-year
|
||||
"Returns the day of the year (1-366).
|
||||
(Date/day-of-year date) ;=> 69"
|
||||
[date])
|
||||
|
||||
(defn day-of-era
|
||||
"Returns the day of the era and era number."
|
||||
[date])
|
||||
|
||||
(defn days-in-month
|
||||
"Returns the number of days in the month.
|
||||
(Date/days-in-month date) ;=> 31"
|
||||
[date])
|
||||
|
||||
(defn months-in-year
|
||||
"Returns the number of months in the year."
|
||||
[date])
|
||||
|
||||
(defn quarter-of-year
|
||||
"Returns the quarter (1-4).
|
||||
(Date/quarter-of-year date) ;=> 1"
|
||||
[date])
|
||||
|
||||
(defn year-of-era
|
||||
"Returns the year of the era."
|
||||
[date])
|
||||
|
||||
(defn leap-year?
|
||||
"Returns true if the date's year is a leap year.
|
||||
(Date/leap-year? date) ;=> true"
|
||||
[date])
|
||||
|
||||
(defn beginning-of-month
|
||||
"Returns the first day of the month.
|
||||
(Date/beginning-of-month date)"
|
||||
[date])
|
||||
|
||||
(defn end-of-month
|
||||
"Returns the last day of the month.
|
||||
(Date/end-of-month date)"
|
||||
[date])
|
||||
|
||||
(defn beginning-of-week
|
||||
"Returns the first day of the week."
|
||||
([date])
|
||||
([date starting-on]))
|
||||
|
||||
(defn end-of-week
|
||||
"Returns the last day of the week."
|
||||
([date])
|
||||
([date starting-on]))
|
||||
|
||||
(defn range
|
||||
"Creates a date range.
|
||||
(Date/range date1 date2) ;=> %Date.Range{}"
|
||||
([first last])
|
||||
([first last step]))
|
||||
|
||||
(defn compare
|
||||
"Compares two dates. Returns :lt, :eq, or :gt."
|
||||
[date1 date2])
|
||||
|
||||
(defn before?
|
||||
"Returns true if `date1` is before `date2`."
|
||||
[date1 date2])
|
||||
|
||||
(defn after?
|
||||
"Returns true if `date1` is after `date2`."
|
||||
[date1 date2])
|
||||
|
||||
(defn shift
|
||||
"Shifts date by a duration.
|
||||
(Date/shift date :month 1 :day -3)"
|
||||
[date duration])
|
||||
|
||||
(defn convert
|
||||
"Converts a date to a different calendar."
|
||||
([date calendar]))
|
||||
|
||||
(defn convert!
|
||||
"Converts to a different calendar. Raises on error."
|
||||
([date calendar]))
|
||||
|
||||
(defn from-erl
|
||||
"Converts Erlang date tuple to Date.
|
||||
(Date/from-erl {2024 3 9}) ;=> {:ok ~D[2024-03-09]}"
|
||||
([tuple])
|
||||
([tuple calendar]))
|
||||
|
||||
(defn from-erl!
|
||||
"Converts Erlang date tuple. Raises on error."
|
||||
([tuple])
|
||||
([tuple calendar]))
|
||||
|
||||
(defn to-erl
|
||||
"Converts Date to Erlang date tuple.
|
||||
(Date/to-erl date) ;=> {2024 3 9}"
|
||||
[date])
|
||||
@@ -0,0 +1,155 @@
|
||||
(ns DateTime
|
||||
"Elixir DateTime module — date and time with timezone.
|
||||
|
||||
In CljElixir: (DateTime/utc-now), (DateTime/to-iso8601 dt), etc.")
|
||||
|
||||
(defn utc-now
|
||||
"Returns the current UTC datetime.
|
||||
(DateTime/utc-now) ;=> ~U[2024-03-09 12:00:00Z]
|
||||
(DateTime/utc-now Calendar.ISO)"
|
||||
([])
|
||||
([calendar]))
|
||||
|
||||
(defn now
|
||||
"Returns the current datetime for a timezone.
|
||||
(DateTime/now \"Etc/UTC\") ;=> {:ok datetime}"
|
||||
([timezone])
|
||||
([timezone calendar]))
|
||||
|
||||
(defn now!
|
||||
"Returns the current datetime. Raises on error.
|
||||
(DateTime/now! \"Etc/UTC\")"
|
||||
([timezone])
|
||||
([timezone calendar]))
|
||||
|
||||
(defn new
|
||||
"Creates a new DateTime.
|
||||
(DateTime/new 2024 3 9 12 0 0)"
|
||||
([date time])
|
||||
([date time timezone])
|
||||
([date time timezone database]))
|
||||
|
||||
(defn new!
|
||||
"Creates a new DateTime. Raises on error."
|
||||
([date time])
|
||||
([date time timezone])
|
||||
([date time timezone database]))
|
||||
|
||||
(defn from-unix
|
||||
"Converts Unix timestamp to DateTime.
|
||||
(DateTime/from-unix 1709985600) ;=> {:ok datetime}
|
||||
(DateTime/from-unix 1709985600000 :millisecond)"
|
||||
([integer])
|
||||
([integer unit])
|
||||
([integer unit calendar]))
|
||||
|
||||
(defn from-unix!
|
||||
"Converts Unix timestamp. Raises on error.
|
||||
(DateTime/from-unix! 1709985600)"
|
||||
([integer])
|
||||
([integer unit])
|
||||
([integer unit calendar]))
|
||||
|
||||
(defn to-unix
|
||||
"Converts DateTime to Unix timestamp.
|
||||
(DateTime/to-unix datetime) ;=> 1709985600
|
||||
(DateTime/to-unix datetime :millisecond)"
|
||||
([datetime])
|
||||
([datetime unit]))
|
||||
|
||||
(defn from-iso8601
|
||||
"Parses ISO 8601 string. Returns {:ok datetime utc-offset}.
|
||||
(DateTime/from-iso8601 \"2024-03-09T12:00:00Z\") ;=> {:ok datetime 0}"
|
||||
([string])
|
||||
([string calendar-or-format]))
|
||||
|
||||
(defn from-iso8601!
|
||||
"Parses ISO 8601. Raises on error."
|
||||
([string])
|
||||
([string calendar-or-format]))
|
||||
|
||||
(defn to-iso8601
|
||||
"Converts to ISO 8601 string.
|
||||
(DateTime/to-iso8601 datetime) ;=> \"2024-03-09T12:00:00Z\""
|
||||
([datetime])
|
||||
([datetime format])
|
||||
([datetime format offset]))
|
||||
|
||||
(defn to-string
|
||||
"Converts to human-readable string.
|
||||
(DateTime/to-string datetime) ;=> \"2024-03-09 12:00:00Z\""
|
||||
[datetime])
|
||||
|
||||
(defn to-date
|
||||
"Extracts the Date part.
|
||||
(DateTime/to-date datetime) ;=> ~D[2024-03-09]"
|
||||
[datetime])
|
||||
|
||||
(defn to-time
|
||||
"Extracts the Time part.
|
||||
(DateTime/to-time datetime) ;=> ~T[12:00:00]"
|
||||
[datetime])
|
||||
|
||||
(defn to-naive
|
||||
"Converts to NaiveDateTime (drops timezone info).
|
||||
(DateTime/to-naive datetime) ;=> ~N[2024-03-09 12:00:00]"
|
||||
[datetime])
|
||||
|
||||
(defn from-naive
|
||||
"Converts NaiveDateTime to DateTime with timezone.
|
||||
(DateTime/from-naive naive \"Etc/UTC\") ;=> {:ok datetime}"
|
||||
([naive-datetime timezone])
|
||||
([naive-datetime timezone database]))
|
||||
|
||||
(defn from-naive!
|
||||
"Converts NaiveDateTime. Raises on error."
|
||||
([naive-datetime timezone])
|
||||
([naive-datetime timezone database]))
|
||||
|
||||
(defn add
|
||||
"Adds `amount` of time to a datetime.
|
||||
(DateTime/add datetime 3600) ;=> +1 hour
|
||||
(DateTime/add datetime 1 :hour)"
|
||||
([datetime amount])
|
||||
([datetime amount unit]))
|
||||
|
||||
(defn diff
|
||||
"Returns the difference between two datetimes.
|
||||
(DateTime/diff dt1 dt2) ;=> seconds
|
||||
(DateTime/diff dt1 dt2 :hour) ;=> hours"
|
||||
([datetime1 datetime2])
|
||||
([datetime1 datetime2 unit]))
|
||||
|
||||
(defn shift-zone
|
||||
"Shifts datetime to a different timezone.
|
||||
(DateTime/shift-zone datetime \"America/New_York\")"
|
||||
([datetime timezone])
|
||||
([datetime timezone database]))
|
||||
|
||||
(defn shift-zone!
|
||||
"Shifts timezone. Raises on error."
|
||||
([datetime timezone])
|
||||
([datetime timezone database]))
|
||||
|
||||
(defn truncate
|
||||
"Truncates datetime to given precision.
|
||||
(DateTime/truncate datetime :second)"
|
||||
[datetime precision])
|
||||
|
||||
(defn compare
|
||||
"Compares two datetimes. Returns :lt, :eq, or :gt.
|
||||
(DateTime/compare dt1 dt2) ;=> :lt"
|
||||
[datetime1 datetime2])
|
||||
|
||||
(defn before?
|
||||
"Returns true if `datetime1` is before `datetime2`."
|
||||
[datetime1 datetime2])
|
||||
|
||||
(defn after?
|
||||
"Returns true if `datetime1` is after `datetime2`."
|
||||
[datetime1 datetime2])
|
||||
|
||||
(defn shift
|
||||
"Shifts datetime by a duration.
|
||||
(DateTime/shift datetime :hour 1 :minute 30)"
|
||||
[datetime duration])
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
(ns Enum
|
||||
"Elixir Enum module — eager operations on enumerables.
|
||||
|
||||
In CljElixir: (Enum/map coll f), (Enum/reduce coll acc f), etc.
|
||||
Works on lists, maps, ranges, and any Enumerable.")
|
||||
|
||||
;; --- Mapping & Transformation ---
|
||||
|
||||
(defn map
|
||||
"Returns a list with `f` applied to each element.
|
||||
(Enum/map [1 2 3] (fn [x] (* x 2))) ;=> [2 4 6]"
|
||||
[enumerable f])
|
||||
|
||||
(defn map-every
|
||||
"Applies `f` to every `nth` element, starting with the first.
|
||||
(Enum/map-every [1 2 3 4 5] 2 (fn [x] (* x 2))) ;=> [2 2 6 4 10]"
|
||||
[enumerable nth f])
|
||||
|
||||
(defn flat-map
|
||||
"Maps `f` over `enumerable` and flattens the result.
|
||||
(Enum/flat-map [[1 2] [3 4]] (fn [x] x)) ;=> [1 2 3 4]"
|
||||
[enumerable f])
|
||||
|
||||
(defn map-reduce
|
||||
"Maps and reduces in one pass. Returns {mapped_list, acc}.
|
||||
(Enum/map-reduce [1 2 3] 0 (fn [x acc] {(* x 2) (+ acc x)})) ;=> {[2 4 6] 6}"
|
||||
[enumerable acc f])
|
||||
|
||||
(defn scan
|
||||
"Returns a list of successive reduced values from the left.
|
||||
(Enum/scan [1 2 3 4] (fn [x acc] (+ x acc))) ;=> [1 3 6 10]"
|
||||
([enumerable f])
|
||||
([enumerable acc f]))
|
||||
|
||||
;; --- Filtering ---
|
||||
|
||||
(defn filter
|
||||
"Returns elements for which `f` returns a truthy value.
|
||||
(Enum/filter [1 2 3 4] (fn [x] (> x 2))) ;=> [3 4]"
|
||||
[enumerable f])
|
||||
|
||||
(defn reject
|
||||
"Returns elements for which `f` returns a falsy value.
|
||||
(Enum/reject [1 2 3 4] (fn [x] (> x 2))) ;=> [1 2]"
|
||||
[enumerable f])
|
||||
|
||||
(defn uniq
|
||||
"Returns unique elements, preserving order.
|
||||
(Enum/uniq [1 2 1 3 2]) ;=> [1 2 3]"
|
||||
[enumerable])
|
||||
|
||||
(defn uniq-by
|
||||
"Returns elements unique by the result of `f`.
|
||||
(Enum/uniq-by [{:x 1 :y 2} {:x 1 :y 3}] (fn [m] (:x m))) ;=> [{:x 1 :y 2}]"
|
||||
[enumerable f])
|
||||
|
||||
(defn dedup
|
||||
"Removes consecutive duplicate elements.
|
||||
(Enum/dedup [1 1 2 2 3 1]) ;=> [1 2 3 1]"
|
||||
[enumerable])
|
||||
|
||||
(defn dedup-by
|
||||
"Removes consecutive elements where `f` returns the same value."
|
||||
[enumerable f])
|
||||
|
||||
;; --- Reducing ---
|
||||
|
||||
(defn reduce
|
||||
"Invokes `f` for each element with the accumulator.
|
||||
(Enum/reduce [1 2 3] 0 (fn [x acc] (+ acc x))) ;=> 6
|
||||
Note: Elixir callback order is (element, acc) not (acc, element)."
|
||||
([enumerable f])
|
||||
([enumerable acc f]))
|
||||
|
||||
(defn reduce-while
|
||||
"Reduces while `f` returns {:cont, acc}. Halts on {:halt, acc}.
|
||||
(Enum/reduce-while [1 2 3 4] 0 (fn [x acc] (if (< acc 5) {:cont (+ acc x)} {:halt acc})))"
|
||||
[enumerable acc f])
|
||||
|
||||
(defn map-join
|
||||
"Maps `f` over `enumerable` then joins results with `joiner`.
|
||||
(Enum/map-join [1 2 3] \", \" (fn [x] (str x))) ;=> \"1, 2, 3\""
|
||||
([enumerable f])
|
||||
([enumerable joiner f]))
|
||||
|
||||
;; --- Sorting ---
|
||||
|
||||
(defn sort
|
||||
"Sorts the enumerable. Uses Erlang term ordering or a custom comparator.
|
||||
(Enum/sort [3 1 2]) ;=> [1 2 3]
|
||||
(Enum/sort [3 1 2] (fn [a b] (> a b))) ;=> [3 2 1]"
|
||||
([enumerable])
|
||||
([enumerable sorter]))
|
||||
|
||||
(defn sort-by
|
||||
"Sorts by the result of applying `mapper` to each element.
|
||||
(Enum/sort-by [{:name \"b\"} {:name \"a\"}] (fn [x] (:name x))) ;=> [{:name \"a\"} {:name \"b\"}]"
|
||||
([enumerable mapper])
|
||||
([enumerable mapper sorter]))
|
||||
|
||||
;; --- Grouping & Partitioning ---
|
||||
|
||||
(defn group-by
|
||||
"Groups elements by the result of `f`.
|
||||
(Enum/group-by [\"ant\" \"bee\" \"ape\"] (fn [s] (String/at s 0)))
|
||||
;=> %{\"a\" => [\"ant\" \"ape\"], \"b\" => [\"bee\"]}"
|
||||
[enumerable f])
|
||||
|
||||
(defn chunk-by
|
||||
"Splits into chunks where consecutive elements return the same value for `f`.
|
||||
(Enum/chunk-by [1 1 2 2 3] (fn [x] x)) ;=> [[1 1] [2 2] [3]]"
|
||||
[enumerable f])
|
||||
|
||||
(defn chunk-every
|
||||
"Splits into chunks of `count` elements.
|
||||
(Enum/chunk-every [1 2 3 4 5] 2) ;=> [[1 2] [3 4] [5]]"
|
||||
([enumerable count])
|
||||
([enumerable count step])
|
||||
([enumerable count step leftover]))
|
||||
|
||||
(defn frequencies
|
||||
"Returns a map with keys as unique elements and values as counts.
|
||||
(Enum/frequencies [\"a\" \"b\" \"a\" \"c\" \"b\" \"a\"]) ;=> %{\"a\" => 3, \"b\" => 2, \"c\" => 1}"
|
||||
[enumerable])
|
||||
|
||||
(defn split-with
|
||||
"Splits into two lists: elements satisfying `f` and the rest.
|
||||
(Enum/split-with [1 2 3 4] (fn [x] (< x 3))) ;=> {[1 2] [3 4]}"
|
||||
[enumerable f])
|
||||
|
||||
(defn partition-by
|
||||
"Splits when `f` returns a new value.
|
||||
(Enum/partition-by [1 1 2 2 3] (fn [x] x)) ;=> [[1 1] [2 2] [3]]"
|
||||
[enumerable f])
|
||||
|
||||
;; --- Lookup & Search ---
|
||||
|
||||
(defn find
|
||||
"Returns the first element for which `f` returns truthy.
|
||||
(Enum/find [1 2 3 4] (fn [x] (> x 2))) ;=> 3"
|
||||
([enumerable f])
|
||||
([enumerable default f]))
|
||||
|
||||
(defn find-index
|
||||
"Returns the index of the first element for which `f` returns truthy.
|
||||
(Enum/find-index [1 2 3] (fn [x] (= x 2))) ;=> 1"
|
||||
[enumerable f])
|
||||
|
||||
(defn find-value
|
||||
"Returns the first truthy return value of `f`.
|
||||
(Enum/find-value [1 2 3] (fn [x] (if (> x 2) (* x 10) nil))) ;=> 30"
|
||||
([enumerable f])
|
||||
([enumerable default f]))
|
||||
|
||||
(defn member?
|
||||
"Returns true if `element` exists in the enumerable.
|
||||
(Enum/member? [1 2 3] 2) ;=> true"
|
||||
[enumerable element])
|
||||
|
||||
(defn any?
|
||||
"Returns true if any element satisfies `f` (or if any element is truthy).
|
||||
(Enum/any? [false nil true]) ;=> true
|
||||
(Enum/any? [1 2 3] (fn [x] (> x 2))) ;=> true"
|
||||
([enumerable])
|
||||
([enumerable f]))
|
||||
|
||||
(defn all?
|
||||
"Returns true if all elements satisfy `f` (or all are truthy).
|
||||
(Enum/all? [1 2 3] (fn [x] (> x 0))) ;=> true"
|
||||
([enumerable])
|
||||
([enumerable f]))
|
||||
|
||||
(defn count
|
||||
"Returns the count of elements, optionally only those satisfying `f`.
|
||||
(Enum/count [1 2 3]) ;=> 3
|
||||
(Enum/count [1 2 3] (fn [x] (> x 1))) ;=> 2"
|
||||
([enumerable])
|
||||
([enumerable f]))
|
||||
|
||||
(defn empty?
|
||||
"Returns true if the enumerable is empty.
|
||||
(Enum/empty? []) ;=> true"
|
||||
[enumerable])
|
||||
|
||||
;; --- Subsequences ---
|
||||
|
||||
(defn take
|
||||
"Takes the first `amount` elements.
|
||||
(Enum/take [1 2 3 4 5] 3) ;=> [1 2 3]"
|
||||
[enumerable amount])
|
||||
|
||||
(defn take-while
|
||||
"Takes elements while `f` returns truthy.
|
||||
(Enum/take-while [1 2 3 4] (fn [x] (< x 3))) ;=> [1 2]"
|
||||
[enumerable f])
|
||||
|
||||
(defn take-every
|
||||
"Takes every `nth` element (0-indexed).
|
||||
(Enum/take-every [1 2 3 4 5 6] 2) ;=> [1 3 5]"
|
||||
[enumerable nth])
|
||||
|
||||
(defn drop
|
||||
"Drops the first `amount` elements.
|
||||
(Enum/drop [1 2 3 4 5] 2) ;=> [3 4 5]"
|
||||
[enumerable amount])
|
||||
|
||||
(defn drop-while
|
||||
"Drops elements while `f` returns truthy.
|
||||
(Enum/drop-while [1 2 3 4] (fn [x] (< x 3))) ;=> [3 4]"
|
||||
[enumerable f])
|
||||
|
||||
(defn slice
|
||||
"Returns a subset of the enumerable.
|
||||
(Enum/slice [1 2 3 4 5] 1 3) ;=> [2 3 4]
|
||||
(Enum/slice [1 2 3 4 5] 1..3) ;=> [2 3 4]"
|
||||
([enumerable index-range])
|
||||
([enumerable start amount]))
|
||||
|
||||
(defn reverse
|
||||
"Reverses the enumerable.
|
||||
(Enum/reverse [1 2 3]) ;=> [3 2 1]"
|
||||
([enumerable])
|
||||
([enumerable tail]))
|
||||
|
||||
(defn shuffle
|
||||
"Returns a list with elements in random order.
|
||||
(Enum/shuffle [1 2 3 4 5]) ;=> [3 1 5 2 4]"
|
||||
[enumerable])
|
||||
|
||||
;; --- Aggregation ---
|
||||
|
||||
(defn sum
|
||||
"Returns the sum of all elements.
|
||||
(Enum/sum [1 2 3]) ;=> 6"
|
||||
[enumerable])
|
||||
|
||||
(defn product
|
||||
"Returns the product of all elements.
|
||||
(Enum/product [1 2 3 4]) ;=> 24"
|
||||
[enumerable])
|
||||
|
||||
(defn min
|
||||
"Returns the minimum element.
|
||||
(Enum/min [3 1 2]) ;=> 1"
|
||||
([enumerable])
|
||||
([enumerable empty-fallback]))
|
||||
|
||||
(defn max
|
||||
"Returns the maximum element.
|
||||
(Enum/max [3 1 2]) ;=> 3"
|
||||
([enumerable])
|
||||
([enumerable empty-fallback]))
|
||||
|
||||
(defn min-by
|
||||
"Returns the element for which `f` returns the smallest value.
|
||||
(Enum/min-by [\"aaa\" \"b\" \"cc\"] (fn [s] (String/length s))) ;=> \"b\""
|
||||
([enumerable f])
|
||||
([enumerable f sorter]))
|
||||
|
||||
(defn max-by
|
||||
"Returns the element for which `f` returns the largest value.
|
||||
(Enum/max-by [\"aaa\" \"b\" \"cc\"] (fn [s] (String/length s))) ;=> \"aaa\""
|
||||
([enumerable f])
|
||||
([enumerable f sorter]))
|
||||
|
||||
(defn min-max
|
||||
"Returns a tuple with the minimum and maximum elements.
|
||||
(Enum/min-max [3 1 2]) ;=> {1 3}"
|
||||
([enumerable])
|
||||
([enumerable empty-fallback]))
|
||||
|
||||
(defn min-max-by
|
||||
"Returns a tuple with the min/max elements by `f`."
|
||||
([enumerable f])
|
||||
([enumerable f sorter-or-empty]))
|
||||
|
||||
;; --- Joining & Conversion ---
|
||||
|
||||
(defn join
|
||||
"Joins elements into a string with an optional separator.
|
||||
(Enum/join [1 2 3] \", \") ;=> \"1, 2, 3\"
|
||||
(Enum/join [1 2 3]) ;=> \"123\""
|
||||
([enumerable])
|
||||
([enumerable joiner]))
|
||||
|
||||
(defn into
|
||||
"Inserts each element into a collectable.
|
||||
(Enum/into [1 2 3] []) ;=> [1 2 3]
|
||||
(Enum/into %{a: 1} %{b: 2}) ;=> %{a: 1, b: 2}
|
||||
(Enum/into [1 2 3] [] (fn [x] (* x 2))) ;=> [2 4 6]"
|
||||
([enumerable collectable])
|
||||
([enumerable collectable transform]))
|
||||
|
||||
(defn to-list
|
||||
"Converts an enumerable to a list.
|
||||
(Enum/to-list (1..5)) ;=> [1 2 3 4 5]"
|
||||
[enumerable])
|
||||
|
||||
(defn zip
|
||||
"Zips corresponding elements from a finite collection of enumerables.
|
||||
(Enum/zip [1 2 3] [:a :b :c]) ;=> [{1 :a} {2 :b} {3 :c}]"
|
||||
([enumerables])
|
||||
([enum1 enum2]))
|
||||
|
||||
(defn zip-with
|
||||
"Zips with a merge function.
|
||||
(Enum/zip-with [1 2 3] [4 5 6] (fn [a b] (+ a b))) ;=> [5 7 9]"
|
||||
([enumerables zip-fun])
|
||||
([enum1 enum2 zip-fun]))
|
||||
|
||||
(defn unzip
|
||||
"Opposite of zip. Takes a list of two-element tuples and returns two lists.
|
||||
(Enum/unzip [{1 :a} {2 :b}]) ;=> {[1 2] [:a :b]}"
|
||||
[list])
|
||||
|
||||
(defn with-index
|
||||
"Wraps each element in a tuple with its index.
|
||||
(Enum/with-index [:a :b :c]) ;=> [{:a 0} {:b 1} {:c 2}]"
|
||||
([enumerable])
|
||||
([enumerable fun-or-offset]))
|
||||
|
||||
(defn flat-map-reduce
|
||||
"Maps and reduces, emitting lists that get flattened. Returns {[flat_mapped], acc}."
|
||||
[enumerable acc f])
|
||||
|
||||
;; --- Element Access ---
|
||||
|
||||
(defn at
|
||||
"Returns the element at `index`. Returns `default` if out of bounds.
|
||||
(Enum/at [1 2 3] 1) ;=> 2
|
||||
(Enum/at [1 2 3] 5 :none) ;=> :none"
|
||||
([enumerable index])
|
||||
([enumerable index default]))
|
||||
|
||||
(defn fetch
|
||||
"Returns {:ok element} or :error for index lookup.
|
||||
(Enum/fetch [1 2 3] 1) ;=> {:ok 2}
|
||||
(Enum/fetch [1 2 3] 5) ;=> :error"
|
||||
[enumerable index])
|
||||
|
||||
(defn fetch!
|
||||
"Returns the element at `index`. Raises if out of bounds.
|
||||
(Enum/fetch! [1 2 3] 1) ;=> 2"
|
||||
[enumerable index])
|
||||
|
||||
(defn random
|
||||
"Returns a random element.
|
||||
(Enum/random [1 2 3 4 5]) ;=> 3"
|
||||
([enumerable])
|
||||
([enumerable count]))
|
||||
|
||||
;; --- List Operations ---
|
||||
|
||||
(defn concat
|
||||
"Concatenates enumerables.
|
||||
(Enum/concat [1 2] [3 4]) ;=> [1 2 3 4]
|
||||
(Enum/concat [[1 2] [3 4]]) ;=> [1 2 3 4]"
|
||||
([enumerables])
|
||||
([left right]))
|
||||
|
||||
(defn intersperse
|
||||
"Inserts `separator` between each element.
|
||||
(Enum/intersperse [1 2 3] 0) ;=> [1 0 2 0 3]"
|
||||
[enumerable separator])
|
||||
|
||||
(defn each
|
||||
"Invokes `f` for each element (side effects). Returns :ok.
|
||||
(Enum/each [1 2 3] (fn [x] (IO/puts x)))"
|
||||
[enumerable f])
|
||||
|
||||
(defn map-intersperse
|
||||
"Maps and intersperses in one pass."
|
||||
[enumerable separator mapper])
|
||||
|
||||
(defn split
|
||||
"Splits into two lists at `count`.
|
||||
(Enum/split [1 2 3 4 5] 3) ;=> {[1 2 3] [4 5]}"
|
||||
[enumerable count])
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
(ns File
|
||||
"Elixir File module — file system operations.
|
||||
|
||||
In CljElixir: (File/read \"path\"), (File/write \"path\" content), etc.
|
||||
Returns {:ok result} or {:error reason} for most operations.")
|
||||
|
||||
(defn read
|
||||
"Reads the contents of `path`. Returns {:ok binary} or {:error reason}.
|
||||
(File/read \"myfile.txt\") ;=> {:ok \"contents\"}"
|
||||
[path])
|
||||
|
||||
(defn read!
|
||||
"Reads the contents of `path`. Raises on error.
|
||||
(File/read! \"myfile.txt\") ;=> \"contents\""
|
||||
[path])
|
||||
|
||||
(defn write
|
||||
"Writes `content` to `path`. Returns :ok or {:error reason}.
|
||||
(File/write \"myfile.txt\" \"hello\")
|
||||
(File/write \"myfile.txt\" \"hello\" [:append])"
|
||||
([path content])
|
||||
([path content modes]))
|
||||
|
||||
(defn write!
|
||||
"Writes `content` to `path`. Raises on error.
|
||||
(File/write! \"myfile.txt\" \"hello\")"
|
||||
([path content])
|
||||
([path content modes]))
|
||||
|
||||
(defn exists?
|
||||
"Returns true if `path` exists.
|
||||
(File/exists? \"myfile.txt\") ;=> true"
|
||||
[path])
|
||||
|
||||
(defn dir?
|
||||
"Returns true if `path` is a directory.
|
||||
(File/dir? \"/tmp\") ;=> true"
|
||||
[path])
|
||||
|
||||
(defn regular?
|
||||
"Returns true if `path` is a regular file.
|
||||
(File/regular? \"myfile.txt\") ;=> true"
|
||||
[path])
|
||||
|
||||
(defn mkdir
|
||||
"Creates a directory at `path`.
|
||||
(File/mkdir \"mydir\") ;=> :ok"
|
||||
[path])
|
||||
|
||||
(defn mkdir-p
|
||||
"Creates a directory and all parent directories.
|
||||
(File/mkdir-p \"a/b/c\") ;=> :ok"
|
||||
[path])
|
||||
|
||||
(defn rm
|
||||
"Removes a file at `path`.
|
||||
(File/rm \"myfile.txt\") ;=> :ok"
|
||||
[path])
|
||||
|
||||
(defn rm!
|
||||
"Removes a file at `path`. Raises on error."
|
||||
[path])
|
||||
|
||||
(defn rm-rf
|
||||
"Removes files and directories recursively.
|
||||
(File/rm-rf \"mydir\") ;=> {:ok [\"mydir/a\" \"mydir\"]}"
|
||||
[path])
|
||||
|
||||
(defn rmdir
|
||||
"Removes an empty directory.
|
||||
(File/rmdir \"mydir\") ;=> :ok"
|
||||
[path])
|
||||
|
||||
(defn cp
|
||||
"Copies `source` to `destination`.
|
||||
(File/cp \"src.txt\" \"dst.txt\") ;=> :ok"
|
||||
([source destination])
|
||||
([source destination callback]))
|
||||
|
||||
(defn cp!
|
||||
"Copies. Raises on error."
|
||||
([source destination])
|
||||
([source destination callback]))
|
||||
|
||||
(defn cp-r
|
||||
"Copies recursively.
|
||||
(File/cp-r \"src_dir\" \"dst_dir\")"
|
||||
([source destination])
|
||||
([source destination callback]))
|
||||
|
||||
(defn cp-r!
|
||||
"Copies recursively. Raises on error."
|
||||
([source destination])
|
||||
([source destination callback]))
|
||||
|
||||
(defn rename
|
||||
"Renames/moves `source` to `destination`.
|
||||
(File/rename \"old.txt\" \"new.txt\") ;=> :ok"
|
||||
[source destination])
|
||||
|
||||
(defn rename!
|
||||
"Renames/moves. Raises on error."
|
||||
[source destination])
|
||||
|
||||
(defn ln-s
|
||||
"Creates a symbolic link.
|
||||
(File/ln-s \"target\" \"link_name\") ;=> :ok"
|
||||
[existing new-link])
|
||||
|
||||
(defn ls
|
||||
"Lists files in a directory. Returns {:ok [filenames]} or {:error reason}.
|
||||
(File/ls \".\") ;=> {:ok [\"mix.exs\" \"lib\"]}"
|
||||
([path])
|
||||
([]))
|
||||
|
||||
(defn ls!
|
||||
"Lists files. Raises on error.
|
||||
(File/ls! \".\") ;=> [\"mix.exs\" \"lib\"]"
|
||||
([path])
|
||||
([]))
|
||||
|
||||
(defn stat
|
||||
"Returns file info. Returns {:ok stat} or {:error reason}.
|
||||
(File/stat \"myfile.txt\") ;=> {:ok %File.Stat{...}}"
|
||||
([path])
|
||||
([path opts]))
|
||||
|
||||
(defn stat!
|
||||
"Returns file info. Raises on error."
|
||||
([path])
|
||||
([path opts]))
|
||||
|
||||
(defn lstat
|
||||
"Like stat but doesn't follow symlinks."
|
||||
([path])
|
||||
([path opts]))
|
||||
|
||||
(defn lstat!
|
||||
"Like stat! but doesn't follow symlinks."
|
||||
([path])
|
||||
([path opts]))
|
||||
|
||||
(defn cwd
|
||||
"Returns the current working directory.
|
||||
(File/cwd) ;=> {:ok \"/Users/ajet/repos/clje\"}"
|
||||
[])
|
||||
|
||||
(defn cwd!
|
||||
"Returns the current working directory. Raises on error."
|
||||
[])
|
||||
|
||||
(defn cd
|
||||
"Changes the current working directory.
|
||||
(File/cd \"/tmp\") ;=> :ok"
|
||||
[path])
|
||||
|
||||
(defn cd!
|
||||
"Changes directory. Raises on error."
|
||||
[path])
|
||||
|
||||
(defn open
|
||||
"Opens a file. Returns {:ok io-device} or {:error reason}.
|
||||
(File/open \"myfile.txt\" [:read :utf8])
|
||||
(File/open \"myfile.txt\" [:write :append])"
|
||||
([path])
|
||||
([path modes]))
|
||||
|
||||
(defn open!
|
||||
"Opens a file. Raises on error."
|
||||
([path])
|
||||
([path modes]))
|
||||
|
||||
(defn close
|
||||
"Closes a file IO device.
|
||||
(File/close io-device) ;=> :ok"
|
||||
[io-device])
|
||||
|
||||
(defn stream
|
||||
"Returns a File.Stream for lazy reading.
|
||||
(File/stream \"bigfile.txt\") ;=> %File.Stream{...}
|
||||
(File/stream \"bigfile.txt\" [:read] :line) ;=> line-by-line"
|
||||
([path])
|
||||
([path modes])
|
||||
([path modes line-or-bytes]))
|
||||
|
||||
(defn stream!
|
||||
"Returns a File.Stream. Raises on error."
|
||||
([path])
|
||||
([path modes])
|
||||
([path modes line-or-bytes]))
|
||||
|
||||
(defn touch
|
||||
"Updates file timestamps, creating the file if it doesn't exist.
|
||||
(File/touch \"myfile.txt\") ;=> :ok"
|
||||
([path])
|
||||
([path time]))
|
||||
|
||||
(defn touch!
|
||||
"Touch. Raises on error."
|
||||
([path])
|
||||
([path time]))
|
||||
|
||||
(defn chmod
|
||||
"Changes file permissions.
|
||||
(File/chmod \"script.sh\" 0o755) ;=> :ok"
|
||||
[path mode])
|
||||
|
||||
(defn chown
|
||||
"Changes file ownership."
|
||||
[path uid])
|
||||
|
||||
(defn chgrp
|
||||
"Changes file group."
|
||||
[path gid])
|
||||
|
||||
(defn read-link
|
||||
"Reads the target of a symbolic link.
|
||||
(File/read-link \"my-link\") ;=> {:ok \"target\"}"
|
||||
[path])
|
||||
|
||||
(defn read-link!
|
||||
"Reads symlink target. Raises on error."
|
||||
[path])
|
||||
@@ -0,0 +1,63 @@
|
||||
(ns Float
|
||||
"Elixir Float module — float operations.
|
||||
|
||||
In CljElixir: (Float/round 3.14159 2), (Float/parse \"3.14\"), etc.")
|
||||
|
||||
(defn parse
|
||||
"Parses a string into a float. Returns {float rest} or :error.
|
||||
(Float/parse \"3.14\") ;=> {3.14 \"\"}
|
||||
(Float/parse \"3.14abc\") ;=> {3.14 \"abc\"}
|
||||
(Float/parse \"nope\") ;=> :error"
|
||||
[string])
|
||||
|
||||
(defn round
|
||||
"Rounds to the given number of decimal places.
|
||||
(Float/round 3.14159 2) ;=> 3.14
|
||||
(Float/round 3.5) ;=> 4.0"
|
||||
([float])
|
||||
([float precision]))
|
||||
|
||||
(defn ceil
|
||||
"Rounds up to given decimal precision.
|
||||
(Float/ceil 3.14 1) ;=> 3.2
|
||||
(Float/ceil 3.14) ;=> 4.0"
|
||||
([float])
|
||||
([float precision]))
|
||||
|
||||
(defn floor
|
||||
"Rounds down to given decimal precision.
|
||||
(Float/floor 3.14 1) ;=> 3.1
|
||||
(Float/floor 3.14) ;=> 3.0"
|
||||
([float])
|
||||
([float precision]))
|
||||
|
||||
(defn to-string
|
||||
"Converts float to string.
|
||||
(Float/to-string 3.14) ;=> \"3.14\"
|
||||
(Float/to-string 3.14 :compact) ;=> compact decimal
|
||||
(Float/to-string 3.14 [:decimals 2]) ;=> \"3.14\""
|
||||
([float])
|
||||
([float opts]))
|
||||
|
||||
(defn to-charlist
|
||||
"Converts float to charlist."
|
||||
([float])
|
||||
([float opts]))
|
||||
|
||||
(defn ratio
|
||||
"Returns {numerator denominator} for the given float.
|
||||
(Float/ratio 0.75) ;=> {3 4}"
|
||||
[float])
|
||||
|
||||
(defn min-finite
|
||||
"Returns the minimum finite float value."
|
||||
[])
|
||||
|
||||
(defn max-finite
|
||||
"Returns the maximum finite float value."
|
||||
[])
|
||||
|
||||
(defn pow
|
||||
"Returns `base` raised to `exponent` as a float.
|
||||
(Float/pow 2.0 10) ;=> 1024.0"
|
||||
[base exponent])
|
||||
@@ -0,0 +1,64 @@
|
||||
(ns GenServer
|
||||
"Elixir GenServer module — generic server (OTP behaviour).
|
||||
|
||||
In CljElixir: (GenServer/start-link MyModule init-arg opts), etc.
|
||||
GenServer is the core abstraction for stateful processes on the BEAM.
|
||||
|
||||
Callbacks to implement in your module:
|
||||
init/1, handle-call/3, handle-cast/2, handle-info/2, terminate/2")
|
||||
|
||||
(defn start
|
||||
"Starts a GenServer without linking. Returns {:ok pid} or {:error reason}.
|
||||
(GenServer/start MyModule init-arg [])
|
||||
(GenServer/start MyModule init-arg :name :my-server)"
|
||||
([module init-arg])
|
||||
([module init-arg opts]))
|
||||
|
||||
(defn start-link
|
||||
"Starts a GenServer linked to the current process. Returns {:ok pid}.
|
||||
(GenServer/start-link MyModule init-arg [])
|
||||
(GenServer/start-link MyModule init-arg :name :my-server)"
|
||||
([module init-arg])
|
||||
([module init-arg opts]))
|
||||
|
||||
(defn call
|
||||
"Makes a synchronous call to the server. Blocks until reply. Default timeout 5000ms.
|
||||
(GenServer/call pid :get-state) ;=> server's reply
|
||||
(GenServer/call pid {:set \"value\"} 10000) ;=> with 10s timeout"
|
||||
([server request])
|
||||
([server request timeout]))
|
||||
|
||||
(defn cast
|
||||
"Sends an asynchronous request to the server. Returns :ok immediately.
|
||||
(GenServer/cast pid {:update \"value\"}) ;=> :ok"
|
||||
[server request])
|
||||
|
||||
(defn reply
|
||||
"Replies to a client from within handle-call (for delayed replies).
|
||||
(GenServer/reply from {:ok result}) ;=> :ok"
|
||||
[client reply])
|
||||
|
||||
(defn stop
|
||||
"Stops the GenServer.
|
||||
(GenServer/stop pid) ;=> :ok
|
||||
(GenServer/stop pid :normal) ;=> with reason
|
||||
(GenServer/stop pid :normal :infinity) ;=> with timeout"
|
||||
([server])
|
||||
([server reason])
|
||||
([server reason timeout]))
|
||||
|
||||
(defn whereis
|
||||
"Returns the PID of a named GenServer, or nil.
|
||||
(GenServer/whereis :my-server) ;=> #PID<0.123.0>"
|
||||
[name])
|
||||
|
||||
(defn multi-call
|
||||
"Calls all locally registered servers on all connected nodes."
|
||||
([name request])
|
||||
([nodes name request])
|
||||
([nodes name request timeout]))
|
||||
|
||||
(defn abcast
|
||||
"Casts to all locally registered servers on all connected nodes."
|
||||
([name request])
|
||||
([nodes name request]))
|
||||
@@ -0,0 +1,83 @@
|
||||
(ns IO
|
||||
"Elixir IO module — input/output operations.
|
||||
|
||||
In CljElixir: (IO/puts msg), (IO/inspect val), etc.
|
||||
Handles reading/writing to stdio, files, and IO devices.")
|
||||
|
||||
(defn puts
|
||||
"Writes `item` to the device followed by a newline. Returns :ok.
|
||||
(IO/puts \"hello\") ;=> prints 'hello\\n', returns :ok
|
||||
(IO/puts :stderr \"error!\") ;=> prints to stderr"
|
||||
([item])
|
||||
([device item]))
|
||||
|
||||
(defn write
|
||||
"Writes `item` to the device without a trailing newline.
|
||||
(IO/write \"hello\") ;=> prints 'hello', returns :ok"
|
||||
([item])
|
||||
([device item]))
|
||||
|
||||
(defn inspect
|
||||
"Inspects the given value and prints it. Returns the value (pass-through).
|
||||
(IO/inspect {:a 1}) ;=> prints '%{a: 1}', returns {:a 1}
|
||||
(IO/inspect val :label \"debug\") ;=> prints 'debug: ...'
|
||||
Useful for debugging — can be inserted anywhere in a pipeline."
|
||||
([item])
|
||||
([item opts])
|
||||
([device item opts]))
|
||||
|
||||
(defn gets
|
||||
"Reads a line from the IO device. Shows `prompt` and returns user input.
|
||||
(IO/gets \"Enter name: \") ;=> \"Alice\\n\""
|
||||
([prompt])
|
||||
([device prompt]))
|
||||
|
||||
(defn read
|
||||
"Reads from the IO device.
|
||||
(IO/read :stdio :line) ;=> reads one line
|
||||
(IO/read :stdio 10) ;=> reads 10 characters"
|
||||
([device count-or-line])
|
||||
([device count-or-line opts]))
|
||||
|
||||
(defn warn
|
||||
"Writes `message` to stderr followed by a newline.
|
||||
(IO/warn \"deprecation warning\")"
|
||||
[message])
|
||||
|
||||
(defn iodata-to-binary
|
||||
"Converts iodata (a list of binaries/integers/iolists) to a single binary.
|
||||
(IO/iodata-to-binary [\"hello\" \" \" \"world\"]) ;=> \"hello world\""
|
||||
[iodata])
|
||||
|
||||
(defn iodata-length
|
||||
"Returns the length of iodata without converting to binary.
|
||||
(IO/iodata-length [\"hello\" \" \" \"world\"]) ;=> 11"
|
||||
[iodata])
|
||||
|
||||
(defn chardata-to-string
|
||||
"Converts chardata to a string."
|
||||
[chardata])
|
||||
|
||||
(defn getn
|
||||
"Gets a number of bytes from IO device.
|
||||
(IO/getn \"prompt> \" 3)"
|
||||
([prompt])
|
||||
([prompt count])
|
||||
([device prompt count]))
|
||||
|
||||
(defn binread
|
||||
"Reads `count` bytes from IO device as binary.
|
||||
(IO/binread :stdio 10)"
|
||||
([device count])
|
||||
([count]))
|
||||
|
||||
(defn binwrite
|
||||
"Writes binary data to IO device.
|
||||
(IO/binwrite :stdio <<1 2 3>>)"
|
||||
([device iodata])
|
||||
([iodata]))
|
||||
|
||||
(defn stream
|
||||
"Converts an IO device into a Stream. Useful for lazy line-by-line reading.
|
||||
(IO/stream :stdio :line)"
|
||||
([device mode]))
|
||||
@@ -0,0 +1,80 @@
|
||||
(ns Integer
|
||||
"Elixir Integer module — integer operations.
|
||||
|
||||
In CljElixir: (Integer/to-string 255 16), (Integer/digits 123), etc.")
|
||||
|
||||
(defn to-string
|
||||
"Converts integer to string, optionally in a given base.
|
||||
(Integer/to-string 123) ;=> \"123\"
|
||||
(Integer/to-string 255 16) ;=> \"FF\""
|
||||
([integer])
|
||||
([integer base]))
|
||||
|
||||
(defn to-charlist
|
||||
"Converts integer to charlist.
|
||||
(Integer/to-charlist 123) ;=> '123'"
|
||||
([integer])
|
||||
([integer base]))
|
||||
|
||||
(defn parse
|
||||
"Parses a string into an integer. Returns {integer rest} or :error.
|
||||
(Integer/parse \"123abc\") ;=> {123 \"abc\"}
|
||||
(Integer/parse \"FF\" 16) ;=> {255 \"\"}
|
||||
(Integer/parse \"nope\") ;=> :error"
|
||||
([string])
|
||||
([string base]))
|
||||
|
||||
(defn digits
|
||||
"Returns the digits of `integer` as a list.
|
||||
(Integer/digits 123) ;=> [1 2 3]
|
||||
(Integer/digits 255 16) ;=> [15 15]"
|
||||
([integer])
|
||||
([integer base]))
|
||||
|
||||
(defn undigits
|
||||
"Converts a list of digits back to an integer.
|
||||
(Integer/undigits [1 2 3]) ;=> 123
|
||||
(Integer/undigits [15 15] 16) ;=> 255"
|
||||
([digits])
|
||||
([digits base]))
|
||||
|
||||
(defn pow
|
||||
"Returns `base` raised to `exponent` (integer exponentiation).
|
||||
(Integer/pow 2 10) ;=> 1024"
|
||||
[base exponent])
|
||||
|
||||
(defn gcd
|
||||
"Returns the greatest common divisor.
|
||||
(Integer/gcd 12 8) ;=> 4"
|
||||
[integer1 integer2])
|
||||
|
||||
(defn mod
|
||||
"Computes modulo (always non-negative for positive divisor).
|
||||
(Integer/mod 10 3) ;=> 1
|
||||
(Integer/mod -5 3) ;=> 1 (differs from rem)"
|
||||
[dividend divisor])
|
||||
|
||||
(defn floor-div
|
||||
"Integer division rounded towards negative infinity.
|
||||
(Integer/floor-div 10 3) ;=> 3
|
||||
(Integer/floor-div -5 3) ;=> -2"
|
||||
[dividend divisor])
|
||||
|
||||
(defn is-odd
|
||||
"Returns true if `integer` is odd. Allowed in guards.
|
||||
(Integer/is-odd 3) ;=> true"
|
||||
[integer])
|
||||
|
||||
(defn is-even
|
||||
"Returns true if `integer` is even. Allowed in guards.
|
||||
(Integer/is-even 4) ;=> true"
|
||||
[integer])
|
||||
|
||||
(defn extended-gcd
|
||||
"Returns {gcd, s, t} such that gcd = s*a + t*b (Bezout's identity).
|
||||
(Integer/extended-gcd 12 8) ;=> {4 1 -1}"
|
||||
[a b])
|
||||
|
||||
(defn to-string-padded
|
||||
"Converts integer to string with zero-padding."
|
||||
[integer width])
|
||||
@@ -0,0 +1,336 @@
|
||||
(ns Kernel
|
||||
"Elixir Kernel module — core functions auto-imported into every module.
|
||||
|
||||
In CljElixir: most Kernel functions are available as builtins (e.g., +, -, if, etc.).
|
||||
Use (Kernel/function ...) for less common ones.
|
||||
Note: many of these are already available as CljElixir builtins without the Kernel/ prefix.")
|
||||
|
||||
;; --- Type Checking ---
|
||||
|
||||
(defn is-atom
|
||||
"Returns true if `term` is an atom.
|
||||
(Kernel/is-atom :hello) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-binary
|
||||
"Returns true if `term` is a binary (string).
|
||||
(Kernel/is-binary \"hello\") ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-bitstring
|
||||
"Returns true if `term` is a bitstring."
|
||||
[term])
|
||||
|
||||
(defn is-boolean
|
||||
"Returns true if `term` is a boolean.
|
||||
(Kernel/is-boolean true) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-float
|
||||
"Returns true if `term` is a float.
|
||||
(Kernel/is-float 1.0) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-function
|
||||
"Returns true if `term` is a function, optionally with given `arity`.
|
||||
(Kernel/is-function f) ;=> true
|
||||
(Kernel/is-function f 2) ;=> true if f accepts 2 args"
|
||||
([term])
|
||||
([term arity]))
|
||||
|
||||
(defn is-integer
|
||||
"Returns true if `term` is an integer.
|
||||
(Kernel/is-integer 42) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-list
|
||||
"Returns true if `term` is a list.
|
||||
(Kernel/is-list [1 2 3]) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-map
|
||||
"Returns true if `term` is a map.
|
||||
(Kernel/is-map {:a 1}) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-map-key
|
||||
"Returns true if `key` exists in `map`. Allowed in guard expressions."
|
||||
[map key])
|
||||
|
||||
(defn is-nil
|
||||
"Returns true if `term` is nil.
|
||||
(Kernel/is-nil nil) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-number
|
||||
"Returns true if `term` is a number (integer or float).
|
||||
(Kernel/is-number 42) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-pid
|
||||
"Returns true if `term` is a PID.
|
||||
(Kernel/is-pid (Process/self)) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-port
|
||||
"Returns true if `term` is a port."
|
||||
[term])
|
||||
|
||||
(defn is-reference
|
||||
"Returns true if `term` is a reference."
|
||||
[term])
|
||||
|
||||
(defn is-tuple
|
||||
"Returns true if `term` is a tuple.
|
||||
(Kernel/is-tuple #el[1 2 3]) ;=> true"
|
||||
[term])
|
||||
|
||||
(defn is-struct
|
||||
"Returns true if `term` is a struct.
|
||||
(Kernel/is-struct term)
|
||||
(Kernel/is-struct term MyStruct) ;=> checks specific struct type"
|
||||
([term])
|
||||
([term name]))
|
||||
|
||||
(defn is-exception
|
||||
"Returns true if `term` is an exception."
|
||||
([term])
|
||||
([term name]))
|
||||
|
||||
;; --- Arithmetic (also available as +, -, *, / builtins) ---
|
||||
|
||||
(defn div
|
||||
"Integer division (truncated).
|
||||
(Kernel/div 10 3) ;=> 3"
|
||||
[dividend divisor])
|
||||
|
||||
(defn rem
|
||||
"Integer remainder (same sign as dividend).
|
||||
(Kernel/rem 10 3) ;=> 1"
|
||||
[dividend divisor])
|
||||
|
||||
(defn abs
|
||||
"Returns the absolute value.
|
||||
(Kernel/abs -5) ;=> 5"
|
||||
[number])
|
||||
|
||||
(defn max
|
||||
"Returns the maximum of two terms.
|
||||
(Kernel/max 1 2) ;=> 2"
|
||||
[first second])
|
||||
|
||||
(defn min
|
||||
"Returns the minimum of two terms.
|
||||
(Kernel/min 1 2) ;=> 1"
|
||||
[first second])
|
||||
|
||||
(defn ceil
|
||||
"Returns the smallest integer >= number.
|
||||
(Kernel/ceil 1.2) ;=> 2"
|
||||
[number])
|
||||
|
||||
(defn floor
|
||||
"Returns the largest integer <= number.
|
||||
(Kernel/floor 1.8) ;=> 1"
|
||||
[number])
|
||||
|
||||
(defn round
|
||||
"Rounds to the nearest integer.
|
||||
(Kernel/round 1.5) ;=> 2"
|
||||
[number])
|
||||
|
||||
(defn trunc
|
||||
"Truncates the float to an integer.
|
||||
(Kernel/trunc 1.9) ;=> 1"
|
||||
[number])
|
||||
|
||||
;; --- Comparison ---
|
||||
|
||||
(defn ==
|
||||
"Structural equality. 1 == 1.0 is true.
|
||||
(Kernel/== 1 1.0) ;=> true"
|
||||
[left right])
|
||||
|
||||
(defn ===
|
||||
"Strict equality. 1 === 1.0 is false.
|
||||
(Kernel/=== 1 1.0) ;=> false"
|
||||
[left right])
|
||||
|
||||
(defn !=
|
||||
"Not equal (structural).
|
||||
(Kernel/!= 1 2) ;=> true"
|
||||
[left right])
|
||||
|
||||
(defn !==
|
||||
"Strict not equal.
|
||||
(Kernel/!== 1 1.0) ;=> true"
|
||||
[left right])
|
||||
|
||||
;; --- String & Binary ---
|
||||
|
||||
(defn to-string
|
||||
"Converts `term` to a string via the String.Chars protocol.
|
||||
(Kernel/to-string 123) ;=> \"123\"
|
||||
(Kernel/to-string :hello) ;=> \"hello\""
|
||||
[term])
|
||||
|
||||
(defn inspect
|
||||
"Returns a string representation of `term` (via Inspect protocol).
|
||||
(Kernel/inspect {:a 1}) ;=> \"%{a: 1}\""
|
||||
([term])
|
||||
([term opts]))
|
||||
|
||||
(defn byte-size
|
||||
"Returns the number of bytes in a binary.
|
||||
(Kernel/byte-size \"hello\") ;=> 5"
|
||||
[binary])
|
||||
|
||||
(defn bit-size
|
||||
"Returns the number of bits in a bitstring."
|
||||
[bitstring])
|
||||
|
||||
(defn binary-part
|
||||
"Extracts a binary part.
|
||||
(Kernel/binary-part \"hello\" 1 3) ;=> \"ell\""
|
||||
([binary start length]))
|
||||
|
||||
;; --- Process & Node ---
|
||||
|
||||
(defn self
|
||||
"Returns the PID of the calling process.
|
||||
(Kernel/self) ;=> #PID<0.123.0>"
|
||||
[])
|
||||
|
||||
(defn node
|
||||
"Returns the current node name, or the node for a given PID/ref.
|
||||
(Kernel/node) ;=> :nonode@nohost"
|
||||
([])
|
||||
([pid-or-ref]))
|
||||
|
||||
(defn spawn
|
||||
"Spawns a new process. Returns PID.
|
||||
(Kernel/spawn (fn [] (IO/puts \"hello\")))"
|
||||
([fun])
|
||||
([module fun args]))
|
||||
|
||||
(defn spawn-link
|
||||
"Spawns a linked process.
|
||||
(Kernel/spawn-link (fn [] (IO/puts \"linked\")))"
|
||||
([fun])
|
||||
([module fun args]))
|
||||
|
||||
(defn spawn-monitor
|
||||
"Spawns a monitored process. Returns {pid ref}."
|
||||
([fun])
|
||||
([module fun args]))
|
||||
|
||||
(defn send
|
||||
"Sends a message. Returns the message.
|
||||
(Kernel/send pid :hello)"
|
||||
[dest msg])
|
||||
|
||||
(defn exit
|
||||
"Sends an exit signal.
|
||||
(Kernel/exit :normal)"
|
||||
[reason])
|
||||
|
||||
;; --- Tuple Operations ---
|
||||
|
||||
(defn elem
|
||||
"Gets element at `index` from a tuple (0-based).
|
||||
(Kernel/elem #el[:a :b :c] 1) ;=> :b"
|
||||
[tuple index])
|
||||
|
||||
(defn put-elem
|
||||
"Puts `value` at `index` in a tuple.
|
||||
(Kernel/put-elem #el[:a :b :c] 1 :x) ;=> #el[:a :x :c]"
|
||||
[tuple index value])
|
||||
|
||||
(defn tuple-size
|
||||
"Returns the number of elements in a tuple.
|
||||
(Kernel/tuple-size #el[1 2 3]) ;=> 3"
|
||||
[tuple])
|
||||
|
||||
(defn make-ref
|
||||
"Creates a unique reference.
|
||||
(Kernel/make-ref) ;=> #Reference<0.0.0.1>"
|
||||
[])
|
||||
|
||||
;; --- List Operations ---
|
||||
|
||||
(defn hd
|
||||
"Returns the head of a list.
|
||||
(Kernel/hd [1 2 3]) ;=> 1"
|
||||
[list])
|
||||
|
||||
(defn tl
|
||||
"Returns the tail of a list.
|
||||
(Kernel/tl [1 2 3]) ;=> [2 3]"
|
||||
[list])
|
||||
|
||||
(defn length
|
||||
"Returns the length of a list.
|
||||
(Kernel/length [1 2 3]) ;=> 3"
|
||||
[list])
|
||||
|
||||
(defn in
|
||||
"Membership test (for use in guards). Checks if `elem` is in `list`.
|
||||
(Kernel/in x [1 2 3])"
|
||||
[elem list])
|
||||
|
||||
;; --- Misc ---
|
||||
|
||||
(defn apply
|
||||
"Applies `fun` with `args`.
|
||||
(Kernel/apply Enum :map [[1 2 3] inc])"
|
||||
([fun args])
|
||||
([module fun args]))
|
||||
|
||||
(defn function-exported?
|
||||
"Returns true if `module` exports `function` with given `arity`.
|
||||
(Kernel/function-exported? Enum :map 2) ;=> true"
|
||||
[module function arity])
|
||||
|
||||
(defn struct
|
||||
"Creates a struct from a module.
|
||||
(Kernel/struct MyStruct {:field \"value\"})"
|
||||
([module])
|
||||
([module fields]))
|
||||
|
||||
(defn struct!
|
||||
"Like struct/2 but raises on invalid keys."
|
||||
([module])
|
||||
([module fields]))
|
||||
|
||||
(defn raise
|
||||
"Raises a RuntimeError or specific exception.
|
||||
(Kernel/raise \"something went wrong\")
|
||||
(Kernel/raise ArgumentError :message \"bad arg\")"
|
||||
([message])
|
||||
([exception attrs]))
|
||||
|
||||
(defn reraise
|
||||
"Re-raises an exception preserving the original stacktrace."
|
||||
([message stacktrace])
|
||||
([exception attrs stacktrace]))
|
||||
|
||||
(defn throw
|
||||
"Throws a value to be caught with try/catch.
|
||||
(Kernel/throw :some-value)"
|
||||
[value])
|
||||
|
||||
(defn tap
|
||||
"Pipes `value` into `fun` for side effects, returns `value`.
|
||||
(Kernel/tap {:a 1} (fn [x] (IO/inspect x))) ;=> {:a 1}"
|
||||
[value fun])
|
||||
|
||||
(defn then
|
||||
"Pipes `value` into `fun`, returns the result of `fun`.
|
||||
(Kernel/then 5 (fn [x] (* x 2))) ;=> 10"
|
||||
[value fun])
|
||||
|
||||
(defn dbg
|
||||
"Debug macro. Prints the code and its result. Returns the result.
|
||||
(Kernel/dbg (+ 1 2)) ;=> prints '(+ 1 2) #=> 3', returns 3"
|
||||
([code])
|
||||
([code opts]))
|
||||
@@ -0,0 +1,165 @@
|
||||
(ns Keyword
|
||||
"Elixir Keyword module — operations on keyword lists (list of {atom, value} tuples).
|
||||
|
||||
In CljElixir: (Keyword/get kw :key), (Keyword/put kw :key val), etc.
|
||||
Keyword lists allow duplicate keys and preserve insertion order.")
|
||||
|
||||
(defn get
|
||||
"Gets the value for `key`. Returns `default` if not found.
|
||||
(Keyword/get [[:a 1] [:b 2]] :a) ;=> 1"
|
||||
([keywords key])
|
||||
([keywords key default]))
|
||||
|
||||
(defn get-lazy
|
||||
"Gets value for `key`, calling `fun` for default if missing."
|
||||
[keywords key fun])
|
||||
|
||||
(defn fetch
|
||||
"Returns {:ok value} or :error.
|
||||
(Keyword/fetch [[:a 1]] :a) ;=> {:ok 1}"
|
||||
[keywords key])
|
||||
|
||||
(defn fetch!
|
||||
"Gets value for `key`. Raises if missing."
|
||||
[keywords key])
|
||||
|
||||
(defn get-values
|
||||
"Gets all values for `key` (keyword lists allow duplicates).
|
||||
(Keyword/get-values [[:a 1] [:a 2] [:b 3]] :a) ;=> [1 2]"
|
||||
[keywords key])
|
||||
|
||||
(defn has-key?
|
||||
"Returns true if `key` exists.
|
||||
(Keyword/has-key? [[:a 1]] :a) ;=> true"
|
||||
[keywords key])
|
||||
|
||||
(defn keys
|
||||
"Returns all keys.
|
||||
(Keyword/keys [[:a 1] [:b 2]]) ;=> [:a :b]"
|
||||
[keywords])
|
||||
|
||||
(defn values
|
||||
"Returns all values.
|
||||
(Keyword/values [[:a 1] [:b 2]]) ;=> [1 2]"
|
||||
[keywords])
|
||||
|
||||
(defn put
|
||||
"Puts `value` under `key`, replacing any existing.
|
||||
(Keyword/put [[:a 1]] :b 2) ;=> [[:a 1] [:b 2]]"
|
||||
[keywords key value])
|
||||
|
||||
(defn put-new
|
||||
"Puts `value` under `key` only if `key` doesn't exist."
|
||||
[keywords key value])
|
||||
|
||||
(defn put-new-lazy
|
||||
"Like put-new but calls `fun` only if key is absent."
|
||||
[keywords key fun])
|
||||
|
||||
(defn delete
|
||||
"Deletes all entries for `key`.
|
||||
(Keyword/delete [[:a 1] [:b 2] [:a 3]] :a) ;=> [[:b 2]]"
|
||||
[keywords key])
|
||||
|
||||
(defn delete-first
|
||||
"Deletes only the first entry for `key`."
|
||||
[keywords key])
|
||||
|
||||
(defn pop
|
||||
"Returns {value, rest} for `key`.
|
||||
(Keyword/pop [[:a 1] [:b 2]] :a) ;=> {1 [[:b 2]]}"
|
||||
([keywords key])
|
||||
([keywords key default]))
|
||||
|
||||
(defn pop-first
|
||||
"Pops only the first entry for `key`."
|
||||
([keywords key])
|
||||
([keywords key default]))
|
||||
|
||||
(defn pop-lazy
|
||||
"Like pop but calls `fun` for default."
|
||||
[keywords key fun])
|
||||
|
||||
(defn pop-values
|
||||
"Pops all values for `key`. Returns {values, rest}."
|
||||
[keywords key])
|
||||
|
||||
(defn update
|
||||
"Updates `key` by applying `fun` to current value.
|
||||
(Keyword/update [[:a 1]] :a (fn [v] (+ v 1))) ;=> [[:a 2]]"
|
||||
([keywords key fun])
|
||||
([keywords key default fun]))
|
||||
|
||||
(defn update!
|
||||
"Updates `key`. Raises if missing."
|
||||
[keywords key fun])
|
||||
|
||||
(defn replace
|
||||
"Replaces value at `key` only if it exists."
|
||||
[keywords key value])
|
||||
|
||||
(defn replace!
|
||||
"Replaces value at `key`. Raises if missing."
|
||||
[keywords key value])
|
||||
|
||||
(defn merge
|
||||
"Merges two keyword lists.
|
||||
(Keyword/merge [[:a 1]] [[:b 2]]) ;=> [[:a 1] [:b 2]]
|
||||
(Keyword/merge kw1 kw2 (fn [k v1 v2] v2)) ;=> with resolver"
|
||||
([keywords1 keywords2])
|
||||
([keywords1 keywords2 fun]))
|
||||
|
||||
(defn split
|
||||
"Splits keyword list into two based on `keys`.
|
||||
(Keyword/split [[:a 1] [:b 2] [:c 3]] [:a :c]) ;=> {[[:a 1] [:c 3]] [[:b 2]]}"
|
||||
[keywords keys])
|
||||
|
||||
(defn take
|
||||
"Takes only the given `keys`.
|
||||
(Keyword/take [[:a 1] [:b 2] [:c 3]] [:a :c]) ;=> [[:a 1] [:c 3]]"
|
||||
[keywords keys])
|
||||
|
||||
(defn drop
|
||||
"Drops the given `keys`."
|
||||
[keywords keys])
|
||||
|
||||
(defn filter
|
||||
"Filters entries where `fun` returns truthy."
|
||||
[keywords fun])
|
||||
|
||||
(defn reject
|
||||
"Rejects entries where `fun` returns truthy."
|
||||
[keywords fun])
|
||||
|
||||
(defn map
|
||||
"Maps over entries. `fun` receives {key, value}."
|
||||
[keywords fun])
|
||||
|
||||
(defn new
|
||||
"Creates a new keyword list.
|
||||
(Keyword/new) ;=> []
|
||||
(Keyword/new [[:a 1] [:b 2]]) ;=> [[:a 1] [:b 2]]"
|
||||
([])
|
||||
([pairs])
|
||||
([pairs transform]))
|
||||
|
||||
(defn keyword?
|
||||
"Returns true if `term` is a keyword list."
|
||||
[term])
|
||||
|
||||
(defn equal?
|
||||
"Returns true if two keyword lists are equal."
|
||||
[keywords1 keywords2])
|
||||
|
||||
(defn to-list
|
||||
"Converts keyword list to a list of tuples (identity for keyword lists)."
|
||||
[keywords])
|
||||
|
||||
(defn validate
|
||||
"Validates keyword list against a set of allowed keys.
|
||||
(Keyword/validate [[:a 1] [:b 2]] [:a :b :c]) ;=> {:ok [[:a 1] [:b 2]]}"
|
||||
[keywords values])
|
||||
|
||||
(defn validate!
|
||||
"Validates. Raises on invalid keys."
|
||||
[keywords values])
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
(ns List
|
||||
"Elixir List module — operations on linked lists.
|
||||
|
||||
In CljElixir: (List/flatten nested), (List/to-tuple lst), etc.
|
||||
Lists are the fundamental sequence type on the BEAM (singly-linked).")
|
||||
|
||||
(defn first
|
||||
"Returns the first element, or `default` if empty.
|
||||
(List/first [1 2 3]) ;=> 1
|
||||
(List/first [] :none) ;=> :none"
|
||||
([list])
|
||||
([list default]))
|
||||
|
||||
(defn last
|
||||
"Returns the last element, or `default` if empty.
|
||||
(List/last [1 2 3]) ;=> 3
|
||||
(List/last [] :none) ;=> :none"
|
||||
([list])
|
||||
([list default]))
|
||||
|
||||
(defn flatten
|
||||
"Flattens nested lists.
|
||||
(List/flatten [[1 [2]] [3 4]]) ;=> [1 2 3 4]
|
||||
(List/flatten [1 [2 [3]]] 1) ;=> [1 2 [3]] (one level only)"
|
||||
([list])
|
||||
([list tail]))
|
||||
|
||||
(defn foldl
|
||||
"Left fold. Invokes `fun` for each element with accumulator.
|
||||
(List/foldl [1 2 3] 0 (fn [elem acc] (+ acc elem))) ;=> 6"
|
||||
[list acc fun])
|
||||
|
||||
(defn foldr
|
||||
"Right fold.
|
||||
(List/foldr [1 2 3] 0 (fn [elem acc] (+ acc elem))) ;=> 6"
|
||||
[list acc fun])
|
||||
|
||||
(defn wrap
|
||||
"Wraps a non-list value in a list. nil becomes []. Lists pass through.
|
||||
(List/wrap 1) ;=> [1]
|
||||
(List/wrap nil) ;=> []
|
||||
(List/wrap [1 2]) ;=> [1 2]"
|
||||
[term])
|
||||
|
||||
(defn duplicate
|
||||
"Creates a list with `elem` repeated `n` times.
|
||||
(List/duplicate :ok 3) ;=> [:ok :ok :ok]"
|
||||
[elem n])
|
||||
|
||||
(defn zip
|
||||
"Zips corresponding elements from multiple lists into tuples.
|
||||
(List/zip [[1 2 3] [:a :b :c]]) ;=> [{1 :a} {2 :b} {3 :c}]"
|
||||
[list-of-lists])
|
||||
|
||||
(defn insert-at
|
||||
"Inserts `value` at `index`.
|
||||
(List/insert-at [1 2 3] 1 :a) ;=> [1 :a 2 3]"
|
||||
[list index value])
|
||||
|
||||
(defn replace-at
|
||||
"Replaces element at `index` with `value`.
|
||||
(List/replace-at [1 2 3] 1 :a) ;=> [1 :a 3]"
|
||||
[list index value])
|
||||
|
||||
(defn update-at
|
||||
"Updates element at `index` by applying `fun`.
|
||||
(List/update-at [1 2 3] 1 (fn [x] (* x 10))) ;=> [1 20 3]"
|
||||
[list index fun])
|
||||
|
||||
(defn delete-at
|
||||
"Deletes element at `index`.
|
||||
(List/delete-at [1 2 3] 1) ;=> [1 3]"
|
||||
[list index])
|
||||
|
||||
(defn delete
|
||||
"Deletes the first occurrence of `element`.
|
||||
(List/delete [1 2 1 3] 1) ;=> [2 1 3]"
|
||||
[list element])
|
||||
|
||||
(defn pop-at
|
||||
"Returns the element at `index` and the list without it.
|
||||
(List/pop-at [1 2 3] 1) ;=> {2 [1 3]}"
|
||||
([list index])
|
||||
([list index default]))
|
||||
|
||||
(defn starts-with?
|
||||
"Returns true if `list` starts with `prefix`.
|
||||
(List/starts-with? [1 2 3] [1 2]) ;=> true"
|
||||
[list prefix])
|
||||
|
||||
(defn myers-difference
|
||||
"Returns the edit steps to transform `list1` into `list2`.
|
||||
(List/myers-difference [1 2 3] [1 3 4]) ;=> [eq: [1], del: [2], eq: [3], ins: [4]]"
|
||||
([list1 list2])
|
||||
([list1 list2 diff-script]))
|
||||
|
||||
(defn to-tuple
|
||||
"Converts a list to a tuple.
|
||||
(List/to-tuple [1 2 3]) ;=> #el[1 2 3]"
|
||||
[list])
|
||||
|
||||
(defn to-string
|
||||
"Converts a charlist to a string.
|
||||
(List/to-string [104 101 108 108 111]) ;=> \"hello\""
|
||||
[charlist])
|
||||
|
||||
(defn to-charlist
|
||||
"Converts a list of codepoints to a charlist."
|
||||
[list])
|
||||
|
||||
(defn to-integer
|
||||
"Converts a charlist to integer.
|
||||
(List/to-integer '123') ;=> 123"
|
||||
([charlist])
|
||||
([charlist base]))
|
||||
|
||||
(defn to-float
|
||||
"Converts a charlist to float."
|
||||
[charlist])
|
||||
|
||||
(defn to-atom
|
||||
"Converts a charlist to atom."
|
||||
[charlist])
|
||||
|
||||
(defn to-existing-atom
|
||||
"Converts a charlist to an existing atom."
|
||||
[charlist])
|
||||
|
||||
(defn ascii-printable?
|
||||
"Returns true if all chars are ASCII printable.
|
||||
(List/ascii-printable? 'hello') ;=> true"
|
||||
([list])
|
||||
([list limit]))
|
||||
|
||||
(defn improper?
|
||||
"Returns true if the list is improper (doesn't end in []).
|
||||
(List/improper? [1 | 2]) ;=> true"
|
||||
[list])
|
||||
|
||||
(defn keyfind
|
||||
"Finds a tuple in a list of tuples where element at `position` matches `key`.
|
||||
(List/keyfind [{:a 1} {:b 2}] :b 0) ;=> {:b 2}
|
||||
(List/keyfind [{:a 1}] :c 0 :default) ;=> :default"
|
||||
([list key position])
|
||||
([list key position default]))
|
||||
|
||||
(defn keystore
|
||||
"Replaces or inserts tuple in list based on key at position."
|
||||
[list position key new-tuple])
|
||||
|
||||
(defn keydelete
|
||||
"Deletes tuple from list where element at position matches key."
|
||||
[list key position])
|
||||
|
||||
(defn keymember?
|
||||
"Returns true if any tuple has `key` at `position`.
|
||||
(List/keymember? [{:a 1} {:b 2}] :a 0) ;=> true"
|
||||
[list key position])
|
||||
|
||||
(defn keyreplace
|
||||
"Replaces the first tuple with matching key at position."
|
||||
[list key position new-tuple])
|
||||
|
||||
(defn keysort
|
||||
"Sorts list of tuples by element at `position`.
|
||||
(List/keysort [{:b 2} {:a 1}] 0) ;=> [{:a 1} {:b 2}]"
|
||||
[list position])
|
||||
|
||||
(defn keytake
|
||||
"Takes all tuples from list where element at position matches key."
|
||||
[list key position])
|
||||
@@ -0,0 +1,127 @@
|
||||
(ns Logger
|
||||
"Elixir Logger module — structured logging.
|
||||
|
||||
In CljElixir: (Logger/info \"message\"), (Logger/debug (fn [] \"lazy\")), etc.
|
||||
Log levels: :emergency :alert :critical :error :warning :notice :info :debug")
|
||||
|
||||
(defn debug
|
||||
"Logs a debug message. Accepts string or zero-arity fn (lazy evaluation).
|
||||
(Logger/debug \"detailed info\")
|
||||
(Logger/debug (fn [] (str \"user=\" user-id)))"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn info
|
||||
"Logs an info message.
|
||||
(Logger/info \"server started on port 4000\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn notice
|
||||
"Logs a notice message.
|
||||
(Logger/notice \"configuration changed\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn warning
|
||||
"Logs a warning message.
|
||||
(Logger/warning \"disk usage above 80%\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn error
|
||||
"Logs an error message.
|
||||
(Logger/error \"failed to connect to database\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn critical
|
||||
"Logs a critical message.
|
||||
(Logger/critical \"system overload detected\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn alert
|
||||
"Logs an alert message.
|
||||
(Logger/alert \"database corruption detected\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn emergency
|
||||
"Logs an emergency message.
|
||||
(Logger/emergency \"system is going down\")"
|
||||
([message-or-fun])
|
||||
([message-or-fun metadata]))
|
||||
|
||||
(defn log
|
||||
"Logs at the specified level.
|
||||
(Logger/log :info \"dynamic level logging\")"
|
||||
([level message-or-fun])
|
||||
([level message-or-fun metadata]))
|
||||
|
||||
(defn configure
|
||||
"Configures the logger at runtime.
|
||||
(Logger/configure :level :debug)"
|
||||
[options])
|
||||
|
||||
(defn level
|
||||
"Returns the current log level.
|
||||
(Logger/level) ;=> :info"
|
||||
[])
|
||||
|
||||
(defn put-module-level
|
||||
"Sets log level for specific modules.
|
||||
(Logger/put-module-level MyModule :debug)"
|
||||
[module level])
|
||||
|
||||
(defn delete-module-level
|
||||
"Resets module-level logging to global.
|
||||
(Logger/delete-module-level MyModule)"
|
||||
[module])
|
||||
|
||||
(defn put-process-level
|
||||
"Sets log level for the current process.
|
||||
(Logger/put-process-level (Process/self) :debug)"
|
||||
[pid level])
|
||||
|
||||
(defn delete-process-level
|
||||
"Resets process-level logging.
|
||||
(Logger/delete-process-level pid)"
|
||||
[pid])
|
||||
|
||||
(defn enable
|
||||
"Enables logging for the current process."
|
||||
[pid])
|
||||
|
||||
(defn disable
|
||||
"Disables logging for the current process."
|
||||
[pid])
|
||||
|
||||
(defn metadata
|
||||
"Gets or sets logger metadata for the current process.
|
||||
(Logger/metadata) ;=> []
|
||||
(Logger/metadata :request-id \"abc123\")"
|
||||
([])
|
||||
([keyword-list]))
|
||||
|
||||
(defn reset-metadata
|
||||
"Resets all logger metadata for the current process.
|
||||
(Logger/reset-metadata) ;=> :ok"
|
||||
([])
|
||||
([keys]))
|
||||
|
||||
(defn flush
|
||||
"Flushes the logger, ensuring all messages are processed.
|
||||
(Logger/flush) ;=> :ok"
|
||||
[])
|
||||
|
||||
(defn add-backend
|
||||
"Adds a logging backend.
|
||||
(Logger/add-backend :console)"
|
||||
([backend])
|
||||
([backend opts]))
|
||||
|
||||
(defn remove-backend
|
||||
"Removes a logging backend."
|
||||
([backend])
|
||||
([backend opts]))
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
(ns Map
|
||||
"Elixir Map module — operations on maps.
|
||||
|
||||
In CljElixir: (Map/get m :key), (Map/put m :key val), etc.
|
||||
Maps are the fundamental key-value data structure on the BEAM.")
|
||||
|
||||
;; --- Access ---
|
||||
|
||||
(defn get
|
||||
"Gets the value for `key` in `map`. Returns `default` if key is missing.
|
||||
(Map/get {:a 1 :b 2} :a) ;=> 1
|
||||
(Map/get {:a 1} :c :not-found) ;=> :not-found"
|
||||
([map key])
|
||||
([map key default]))
|
||||
|
||||
(defn get-lazy
|
||||
"Gets `key` from `map`, calling `fun` for default if missing.
|
||||
(Map/get-lazy m :key (fn [] (expensive-computation)))"
|
||||
[map key fun])
|
||||
|
||||
(defn fetch
|
||||
"Returns {:ok value} if `key` exists, :error otherwise.
|
||||
(Map/fetch {:a 1} :a) ;=> {:ok 1}
|
||||
(Map/fetch {:a 1} :b) ;=> :error"
|
||||
[map key])
|
||||
|
||||
(defn fetch!
|
||||
"Gets the value for `key`. Raises if key is missing.
|
||||
(Map/fetch! {:a 1} :a) ;=> 1"
|
||||
[map key])
|
||||
|
||||
(defn has-key?
|
||||
"Returns true if `map` contains `key`.
|
||||
(Map/has-key? {:a 1 :b 2} :a) ;=> true"
|
||||
[map key])
|
||||
|
||||
(defn keys
|
||||
"Returns all keys in the map.
|
||||
(Map/keys {:a 1 :b 2}) ;=> [:a :b]"
|
||||
[map])
|
||||
|
||||
(defn values
|
||||
"Returns all values in the map.
|
||||
(Map/values {:a 1 :b 2}) ;=> [1 2]"
|
||||
[map])
|
||||
|
||||
;; --- Modification ---
|
||||
|
||||
(defn put
|
||||
"Puts the given `value` under `key` in `map`.
|
||||
(Map/put {:a 1} :b 2) ;=> {:a 1 :b 2}"
|
||||
[map key value])
|
||||
|
||||
(defn put-new
|
||||
"Puts `value` under `key` only if `key` doesn't exist yet.
|
||||
(Map/put-new {:a 1} :a 99) ;=> {:a 1}
|
||||
(Map/put-new {:a 1} :b 2) ;=> {:a 1 :b 2}"
|
||||
[map key value])
|
||||
|
||||
(defn put-new-lazy
|
||||
"Like put-new but calls `fun` only if key is absent.
|
||||
(Map/put-new-lazy m :key (fn [] (expensive-computation)))"
|
||||
[map key fun])
|
||||
|
||||
(defn delete
|
||||
"Deletes `key` from `map`. No-op if key doesn't exist.
|
||||
(Map/delete {:a 1 :b 2} :a) ;=> {:b 2}"
|
||||
[map key])
|
||||
|
||||
(defn drop
|
||||
"Drops the given `keys` from `map`.
|
||||
(Map/drop {:a 1 :b 2 :c 3} [:a :c]) ;=> {:b 2}"
|
||||
[map keys])
|
||||
|
||||
(defn take
|
||||
"Takes only the given `keys` from `map`.
|
||||
(Map/take {:a 1 :b 2 :c 3} [:a :c]) ;=> {:a 1 :c 3}"
|
||||
[map keys])
|
||||
|
||||
(defn pop
|
||||
"Returns the value for `key` and the map without `key`.
|
||||
(Map/pop {:a 1 :b 2} :a) ;=> {1 {:b 2}}
|
||||
(Map/pop {:a 1} :c :default) ;=> {:default {:a 1}}"
|
||||
([map key])
|
||||
([map key default]))
|
||||
|
||||
(defn pop-lazy
|
||||
"Like pop but calls `fun` for default if key is absent."
|
||||
[map key fun])
|
||||
|
||||
(defn update
|
||||
"Updates the value at `key` by applying `fun` to the current value.
|
||||
(Map/update {:a 1} :a (fn [v] (+ v 1))) ;=> {:a 2}"
|
||||
([map key fun])
|
||||
([map key default fun]))
|
||||
|
||||
(defn update!
|
||||
"Updates `key` by applying `fun`. Raises if `key` doesn't exist.
|
||||
(Map/update! {:a 1} :a (fn [v] (* v 2))) ;=> {:a 2}"
|
||||
[map key fun])
|
||||
|
||||
(defn replace
|
||||
"Replaces value at `key` only if it already exists. No-op otherwise.
|
||||
(Map/replace {:a 1} :a 99) ;=> {:a 99}
|
||||
(Map/replace {:a 1} :b 99) ;=> {:a 1}"
|
||||
[map key value])
|
||||
|
||||
(defn replace!
|
||||
"Replaces value at `key`. Raises if `key` doesn't exist."
|
||||
[map key value])
|
||||
|
||||
;; --- Merging ---
|
||||
|
||||
(defn merge
|
||||
"Merges two maps. Values from `map2` take precedence.
|
||||
(Map/merge {:a 1 :b 2} {:b 3 :c 4}) ;=> {:a 1 :b 3 :c 4}
|
||||
With resolver: (Map/merge m1 m2 (fn [k v1 v2] (+ v1 v2)))"
|
||||
([map1 map2])
|
||||
([map1 map2 resolver]))
|
||||
|
||||
(defn split
|
||||
"Splits `map` into two maps based on the given `keys`.
|
||||
(Map/split {:a 1 :b 2 :c 3} [:a :c]) ;=> {%{a: 1, c: 3} %{b: 2}}"
|
||||
[map keys])
|
||||
|
||||
;; --- Conversion ---
|
||||
|
||||
(defn new
|
||||
"Creates a new empty map or from an enumerable.
|
||||
(Map/new) ;=> {}
|
||||
(Map/new [[:a 1] [:b 2]]) ;=> {:a 1 :b 2}
|
||||
(Map/new [1 2 3] (fn [x] {x (* x x)})) ;=> {1 1, 2 4, 3 9}"
|
||||
([])
|
||||
([enumerable])
|
||||
([enumerable transform]))
|
||||
|
||||
(defn from-struct
|
||||
"Converts a struct to a plain map (removes __struct__ key).
|
||||
(Map/from-struct my-struct) ;=> {...}"
|
||||
[struct])
|
||||
|
||||
(defn to-list
|
||||
"Converts a map to a keyword list (list of {key, value} tuples).
|
||||
(Map/to-list {:a 1 :b 2}) ;=> [[:a 1] [:b 2]]"
|
||||
[map])
|
||||
|
||||
(defn equal?
|
||||
"Returns true if two maps are equal.
|
||||
(Map/equal? {:a 1} {:a 1}) ;=> true"
|
||||
[map1 map2])
|
||||
|
||||
;; --- Filtering ---
|
||||
|
||||
(defn filter
|
||||
"Filters map entries where `f` returns truthy. `f` receives {key, value}.
|
||||
(Map/filter {:a 1 :b 2 :c 3} (fn [{k v}] (> v 1))) ;=> {:b 2 :c 3}"
|
||||
[map f])
|
||||
|
||||
(defn reject
|
||||
"Rejects map entries where `f` returns truthy.
|
||||
(Map/reject {:a 1 :b 2 :c 3} (fn [{k v}] (> v 1))) ;=> {:a 1}"
|
||||
[map f])
|
||||
|
||||
(defn map
|
||||
"Maps over entries, `f` receives {key, value} and must return {key, value}.
|
||||
(Map/map {:a 1 :b 2} (fn [{k v}] {k (* v 10)})) ;=> {:a 10 :b 20}"
|
||||
[map f])
|
||||
|
||||
(defn intersect
|
||||
"Returns entries common to both maps (using `map2` values)."
|
||||
([map1 map2])
|
||||
([map1 map2 resolver]))
|
||||
|
||||
(defn diff
|
||||
"Returns {entries_only_in_map1, entries_only_in_map2, entries_in_both}."
|
||||
[map1 map2])
|
||||
@@ -0,0 +1,87 @@
|
||||
(ns MapSet
|
||||
"Elixir MapSet module — set operations backed by maps.
|
||||
|
||||
In CljElixir: (MapSet/new [1 2 3]), (MapSet/member? s 2), etc.
|
||||
Use #{1 2 3} literal syntax for set creation in CljElixir.")
|
||||
|
||||
(defn new
|
||||
"Creates a new set, optionally from an enumerable.
|
||||
(MapSet/new) ;=> #{}
|
||||
(MapSet/new [1 2 3]) ;=> #{1 2 3}
|
||||
(MapSet/new [1 2 3] (fn [x] (* x 2))) ;=> #{2 4 6}"
|
||||
([])
|
||||
([enumerable])
|
||||
([enumerable transform]))
|
||||
|
||||
(defn put
|
||||
"Inserts `value` into the set.
|
||||
(MapSet/put #{1 2} 3) ;=> #{1 2 3}"
|
||||
[set value])
|
||||
|
||||
(defn delete
|
||||
"Deletes `value` from the set.
|
||||
(MapSet/delete #{1 2 3} 2) ;=> #{1 3}"
|
||||
[set value])
|
||||
|
||||
(defn member?
|
||||
"Returns true if `value` is in `set`.
|
||||
(MapSet/member? #{1 2 3} 2) ;=> true"
|
||||
[set value])
|
||||
|
||||
(defn size
|
||||
"Returns the number of elements.
|
||||
(MapSet/size #{1 2 3}) ;=> 3"
|
||||
[set])
|
||||
|
||||
(defn to-list
|
||||
"Converts the set to a list.
|
||||
(MapSet/to-list #{1 2 3}) ;=> [1 2 3]"
|
||||
[set])
|
||||
|
||||
(defn equal?
|
||||
"Returns true if two sets are equal.
|
||||
(MapSet/equal? #{1 2} #{2 1}) ;=> true"
|
||||
[set1 set2])
|
||||
|
||||
(defn union
|
||||
"Returns the union of two sets.
|
||||
(MapSet/union #{1 2} #{2 3}) ;=> #{1 2 3}"
|
||||
[set1 set2])
|
||||
|
||||
(defn intersection
|
||||
"Returns the intersection of two sets.
|
||||
(MapSet/intersection #{1 2 3} #{2 3 4}) ;=> #{2 3}"
|
||||
[set1 set2])
|
||||
|
||||
(defn difference
|
||||
"Returns elements in `set1` not in `set2`.
|
||||
(MapSet/difference #{1 2 3} #{2 3}) ;=> #{1}"
|
||||
[set1 set2])
|
||||
|
||||
(defn symmetric-difference
|
||||
"Returns elements in either set but not both.
|
||||
(MapSet/symmetric-difference #{1 2 3} #{2 3 4}) ;=> #{1 4}"
|
||||
[set1 set2])
|
||||
|
||||
(defn subset?
|
||||
"Returns true if `set1` is a subset of `set2`.
|
||||
(MapSet/subset? #{1 2} #{1 2 3}) ;=> true"
|
||||
[set1 set2])
|
||||
|
||||
(defn disjoint?
|
||||
"Returns true if `set1` and `set2` have no elements in common.
|
||||
(MapSet/disjoint? #{1 2} #{3 4}) ;=> true"
|
||||
[set1 set2])
|
||||
|
||||
(defn filter
|
||||
"Returns elements for which `fun` returns truthy.
|
||||
(MapSet/filter #{1 2 3 4} (fn [x] (> x 2))) ;=> #{3 4}"
|
||||
[set fun])
|
||||
|
||||
(defn reject
|
||||
"Returns elements for which `fun` returns falsy."
|
||||
[set fun])
|
||||
|
||||
(defn map
|
||||
"Maps `fun` over the set, returning a new set."
|
||||
[set fun])
|
||||
@@ -0,0 +1,76 @@
|
||||
(ns Module
|
||||
"Elixir Module module — module introspection and manipulation.
|
||||
|
||||
In CljElixir: (Module/defines? MyModule :my-func 2), etc.")
|
||||
|
||||
(defn concat
|
||||
"Concatenates atoms/strings into a module name.
|
||||
(Module/concat [\"Elixir\" \"MyApp\" \"Router\"]) ;=> MyApp.Router
|
||||
(Module/concat Elixir.MyApp :Router) ;=> MyApp.Router"
|
||||
([list])
|
||||
([left right]))
|
||||
|
||||
(defn split
|
||||
"Splits a module name into parts.
|
||||
(Module/split MyApp.Router) ;=> [\"MyApp\" \"Router\"]"
|
||||
[module])
|
||||
|
||||
(defn defines?
|
||||
"Returns true if `module` defines `function` with given `arity`.
|
||||
(Module/defines? Enum :map 2) ;=> true
|
||||
(Module/defines? Enum :map) ;=> true"
|
||||
([module function-name])
|
||||
([module function-name arity]))
|
||||
|
||||
(defn definitions-in
|
||||
"Returns all functions/macros defined in `module`.
|
||||
(Module/definitions-in Enum) ;=> [{:map 2} {:filter 2} ...]"
|
||||
([module])
|
||||
([module kind]))
|
||||
|
||||
(defn has-attribute?
|
||||
"Returns true if `module` has `attribute`.
|
||||
(Module/has-attribute? MyModule :behaviour) ;=> true"
|
||||
[module attribute])
|
||||
|
||||
(defn get-attribute
|
||||
"Gets a module attribute.
|
||||
(Module/get-attribute MyModule :moduledoc)"
|
||||
([module attribute])
|
||||
([module attribute default]))
|
||||
|
||||
(defn put-attribute
|
||||
"Sets a module attribute during compilation."
|
||||
[module attribute value])
|
||||
|
||||
(defn delete-attribute
|
||||
"Deletes a module attribute during compilation."
|
||||
[module attribute])
|
||||
|
||||
(defn register-attribute
|
||||
"Registers a module attribute during compilation."
|
||||
[module attribute opts])
|
||||
|
||||
(defn spec-to-callback
|
||||
"Converts a spec to a callback."
|
||||
[module spec])
|
||||
|
||||
(defn open?
|
||||
"Returns true if the module is open (being compiled)."
|
||||
[module])
|
||||
|
||||
(defn overridable?
|
||||
"Returns true if function is overridable in module."
|
||||
[module function-arity])
|
||||
|
||||
(defn make-overridable
|
||||
"Makes functions overridable."
|
||||
[module function-arities])
|
||||
|
||||
(defn safe-concat
|
||||
"Safely concatenates module atoms without creating new atoms."
|
||||
[list])
|
||||
|
||||
(defn create
|
||||
"Creates a module at runtime."
|
||||
[module quoted opts])
|
||||
@@ -0,0 +1,126 @@
|
||||
(ns NaiveDateTime
|
||||
"Elixir NaiveDateTime module — datetime without timezone.
|
||||
|
||||
In CljElixir: (NaiveDateTime/utc-now), (NaiveDateTime/new 2024 3 9 12 0 0), etc.
|
||||
'Naive' because it has no timezone information.")
|
||||
|
||||
(defn utc-now
|
||||
"Returns the current UTC naive datetime.
|
||||
(NaiveDateTime/utc-now) ;=> ~N[2024-03-09 12:00:00]"
|
||||
([])
|
||||
([calendar]))
|
||||
|
||||
(defn local-now
|
||||
"Returns the current local naive datetime."
|
||||
([])
|
||||
([calendar]))
|
||||
|
||||
(defn new
|
||||
"Creates a new NaiveDateTime.
|
||||
(NaiveDateTime/new 2024 3 9 12 0 0) ;=> {:ok ~N[2024-03-09 12:00:00]}"
|
||||
([year month day hour minute second])
|
||||
([year month day hour minute second microsecond])
|
||||
([date time]))
|
||||
|
||||
(defn new!
|
||||
"Creates a new NaiveDateTime. Raises on error."
|
||||
([year month day hour minute second])
|
||||
([year month day hour minute second microsecond])
|
||||
([date time]))
|
||||
|
||||
(defn from-iso8601
|
||||
"Parses ISO 8601 string.
|
||||
(NaiveDateTime/from-iso8601 \"2024-03-09T12:00:00\") ;=> {:ok naive}"
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn from-iso8601!
|
||||
"Parses ISO 8601. Raises on error."
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn to-iso8601
|
||||
"Converts to ISO 8601 string.
|
||||
(NaiveDateTime/to-iso8601 ndt) ;=> \"2024-03-09T12:00:00\""
|
||||
([naive-datetime])
|
||||
([naive-datetime format]))
|
||||
|
||||
(defn to-string
|
||||
"Converts to string."
|
||||
[naive-datetime])
|
||||
|
||||
(defn to-date
|
||||
"Extracts the Date part."
|
||||
[naive-datetime])
|
||||
|
||||
(defn to-time
|
||||
"Extracts the Time part."
|
||||
[naive-datetime])
|
||||
|
||||
(defn add
|
||||
"Adds time.
|
||||
(NaiveDateTime/add ndt 3600) ;=> +1 hour"
|
||||
([naive-datetime amount])
|
||||
([naive-datetime amount unit]))
|
||||
|
||||
(defn diff
|
||||
"Returns difference.
|
||||
(NaiveDateTime/diff ndt1 ndt2) ;=> seconds"
|
||||
([naive-datetime1 naive-datetime2])
|
||||
([naive-datetime1 naive-datetime2 unit]))
|
||||
|
||||
(defn truncate
|
||||
"Truncates to precision."
|
||||
[naive-datetime precision])
|
||||
|
||||
(defn compare
|
||||
"Compares two NaiveDateTimes. Returns :lt, :eq, or :gt."
|
||||
[naive-datetime1 naive-datetime2])
|
||||
|
||||
(defn before?
|
||||
"Returns true if first is before second."
|
||||
[naive-datetime1 naive-datetime2])
|
||||
|
||||
(defn after?
|
||||
"Returns true if first is after second."
|
||||
[naive-datetime1 naive-datetime2])
|
||||
|
||||
(defn shift
|
||||
"Shifts by a duration."
|
||||
[naive-datetime duration])
|
||||
|
||||
(defn beginning-of-day
|
||||
"Returns midnight of the same day."
|
||||
[naive-datetime])
|
||||
|
||||
(defn end-of-day
|
||||
"Returns 23:59:59.999999 of the same day."
|
||||
[naive-datetime])
|
||||
|
||||
(defn from-erl
|
||||
"Converts from Erlang datetime tuple.
|
||||
(NaiveDateTime/from-erl {{2024 3 9} {12 0 0}}) ;=> {:ok naive}"
|
||||
([tuple])
|
||||
([tuple microsecond])
|
||||
([tuple microsecond calendar]))
|
||||
|
||||
(defn from-erl!
|
||||
"Converts from Erlang tuple. Raises on error."
|
||||
([tuple])
|
||||
([tuple microsecond])
|
||||
([tuple microsecond calendar]))
|
||||
|
||||
(defn to-erl
|
||||
"Converts to Erlang datetime tuple.
|
||||
(NaiveDateTime/to-erl ndt) ;=> {{2024 3 9} {12 0 0}}"
|
||||
[naive-datetime])
|
||||
|
||||
(defn from-gregorian-seconds
|
||||
"Creates from seconds since year 0."
|
||||
([seconds])
|
||||
([seconds microsecond])
|
||||
([seconds microsecond calendar]))
|
||||
|
||||
(defn to-gregorian-seconds
|
||||
"Returns seconds since year 0."
|
||||
[naive-datetime])
|
||||
@@ -0,0 +1,88 @@
|
||||
(ns Node
|
||||
"Elixir Node module — distributed Erlang node operations.
|
||||
|
||||
In CljElixir: (Node/self), (Node/connect :other@host), etc.
|
||||
For building distributed BEAM applications.")
|
||||
|
||||
(defn self
|
||||
"Returns the current node name.
|
||||
(Node/self) ;=> :mynode@myhost"
|
||||
[])
|
||||
|
||||
(defn alive?
|
||||
"Returns true if the local node is alive (part of a distributed system).
|
||||
(Node/alive?) ;=> true"
|
||||
[])
|
||||
|
||||
(defn list
|
||||
"Returns a list of all connected nodes.
|
||||
(Node/list) ;=> [:node1@host :node2@host]
|
||||
(Node/list :known) ;=> includes known but disconnected nodes"
|
||||
([])
|
||||
([type]))
|
||||
|
||||
(defn connect
|
||||
"Connects to a remote node.
|
||||
(Node/connect :other@hostname) ;=> true"
|
||||
[node])
|
||||
|
||||
(defn disconnect
|
||||
"Disconnects from a remote node.
|
||||
(Node/disconnect :other@hostname) ;=> true"
|
||||
[node])
|
||||
|
||||
(defn monitor
|
||||
"Monitors a remote node. Sends {:nodedown node} on disconnect.
|
||||
(Node/monitor :other@hostname true)"
|
||||
([node flag])
|
||||
([node flag opts]))
|
||||
|
||||
(defn ping
|
||||
"Pings a remote node.
|
||||
(Node/ping :other@hostname) ;=> :pong or :pang"
|
||||
[node])
|
||||
|
||||
(defn spawn
|
||||
"Spawns a process on a remote node.
|
||||
(Node/spawn :other@hostname (fn [] (IO/puts \"remote!\")))"
|
||||
([node fun])
|
||||
([node fun opts])
|
||||
([node module fun args])
|
||||
([node module fun args opts]))
|
||||
|
||||
(defn spawn-link
|
||||
"Spawns a linked process on a remote node."
|
||||
([node fun])
|
||||
([node fun opts])
|
||||
([node module fun args])
|
||||
([node module fun args opts]))
|
||||
|
||||
(defn spawn-monitor
|
||||
"Spawns a monitored process on a remote node."
|
||||
([node fun])
|
||||
([node fun opts])
|
||||
([node module fun args])
|
||||
([node module fun args opts]))
|
||||
|
||||
(defn get-cookie
|
||||
"Returns the magic cookie for the node.
|
||||
(Node/get-cookie) ;=> :nocookie or :somecookie"
|
||||
[])
|
||||
|
||||
(defn set-cookie
|
||||
"Sets the magic cookie.
|
||||
(Node/set-cookie :my-secret-cookie)"
|
||||
([cookie])
|
||||
([node cookie]))
|
||||
|
||||
(defn start
|
||||
"Starts distribution. Returns {:ok pid} or {:error reason}.
|
||||
(Node/start :mynode :shortnames)"
|
||||
([name])
|
||||
([name type])
|
||||
([name type tick-time]))
|
||||
|
||||
(defn stop
|
||||
"Stops distribution.
|
||||
(Node/stop) ;=> :ok"
|
||||
[])
|
||||
@@ -0,0 +1,90 @@
|
||||
(ns Path
|
||||
"Elixir Path module — file path manipulation.
|
||||
|
||||
In CljElixir: (Path/join \"a\" \"b\"), (Path/expand \"~/file\"), etc.
|
||||
All functions work with forward slashes on all platforms.")
|
||||
|
||||
(defn join
|
||||
"Joins path segments.
|
||||
(Path/join \"a\" \"b\") ;=> \"a/b\"
|
||||
(Path/join [\"a\" \"b\" \"c\"]) ;=> \"a/b/c\""
|
||||
([paths])
|
||||
([left right]))
|
||||
|
||||
(defn expand
|
||||
"Expands a path to an absolute path, resolving ~ and relative components.
|
||||
(Path/expand \"~/file.txt\") ;=> \"/Users/ajet/file.txt\"
|
||||
(Path/expand \"../other\" \"/base\") ;=> \"/other\""
|
||||
([path])
|
||||
([path relative-to]))
|
||||
|
||||
(defn absname
|
||||
"Converts to absolute path.
|
||||
(Path/absname \"file.txt\") ;=> \"/Users/ajet/repos/clje/file.txt\""
|
||||
([path])
|
||||
([path relative-to]))
|
||||
|
||||
(defn relative-to
|
||||
"Returns the relative path from `path` to `from`.
|
||||
(Path/relative-to \"/a/b/c\" \"/a\") ;=> \"b/c\""
|
||||
([path from])
|
||||
([path from opts]))
|
||||
|
||||
(defn relative
|
||||
"Forces path to be relative.
|
||||
(Path/relative \"/a/b\") ;=> \"a/b\""
|
||||
[path])
|
||||
|
||||
(defn basename
|
||||
"Returns the last component of the path.
|
||||
(Path/basename \"/a/b/c.txt\") ;=> \"c.txt\"
|
||||
(Path/basename \"/a/b/c.txt\" \".txt\") ;=> \"c\""
|
||||
([path])
|
||||
([path extension]))
|
||||
|
||||
(defn dirname
|
||||
"Returns the directory component.
|
||||
(Path/dirname \"/a/b/c.txt\") ;=> \"/a/b\""
|
||||
[path])
|
||||
|
||||
(defn extname
|
||||
"Returns the file extension.
|
||||
(Path/extname \"file.ex\") ;=> \".ex\"
|
||||
(Path/extname \"file\") ;=> \"\""
|
||||
[path])
|
||||
|
||||
(defn rootname
|
||||
"Returns the path without extension.
|
||||
(Path/rootname \"file.ex\") ;=> \"file\"
|
||||
(Path/rootname \"file.tar.gz\" \".tar.gz\") ;=> \"file\""
|
||||
([path])
|
||||
([path extension]))
|
||||
|
||||
(defn split
|
||||
"Splits a path into its components.
|
||||
(Path/split \"/a/b/c\") ;=> [\"/\" \"a\" \"b\" \"c\"]"
|
||||
[path])
|
||||
|
||||
(defn type
|
||||
"Returns the path type: :absolute, :relative, or :volumerelative.
|
||||
(Path/type \"/a/b\") ;=> :absolute
|
||||
(Path/type \"a/b\") ;=> :relative"
|
||||
[path])
|
||||
|
||||
(defn wildcard
|
||||
"Expands a glob pattern. Returns matching paths.
|
||||
(Path/wildcard \"lib/**/*.ex\") ;=> [\"lib/my_app.ex\" ...]
|
||||
(Path/wildcard \"*.{ex,exs}\") ;=> matches .ex and .exs files"
|
||||
([glob])
|
||||
([glob opts]))
|
||||
|
||||
(defn safe-relative
|
||||
"Returns a safe relative path (no ..). Returns {:ok path} or :error.
|
||||
(Path/safe-relative \"a/b/c\") ;=> {:ok \"a/b/c\"}
|
||||
(Path/safe-relative \"../etc/passwd\") ;=> :error"
|
||||
([path])
|
||||
([path cwd]))
|
||||
|
||||
(defn safe-relative-to
|
||||
"Returns a safe relative path from `path` to `cwd`. Returns {:ok path} or :error."
|
||||
[path cwd])
|
||||
@@ -0,0 +1,49 @@
|
||||
(ns Port
|
||||
"Elixir Port module — external program interaction.
|
||||
|
||||
In CljElixir: (Port/open {:spawn \"cmd\"} opts), etc.
|
||||
Ports allow communication with external OS processes via stdin/stdout.")
|
||||
|
||||
(defn open
|
||||
"Opens a port to an external program. Returns a port.
|
||||
(Port/open {:spawn \"cat\"} [:binary])
|
||||
(Port/open {:spawn-executable \"/usr/bin/python3\"} [:binary {:args [\"-c\" \"print(1)\"]}])
|
||||
(Port/open {:fd 0 1} [:binary]) ;=> stdin/stdout"
|
||||
[name settings])
|
||||
|
||||
(defn command
|
||||
"Sends data to a port.
|
||||
(Port/command port data)"
|
||||
([port data])
|
||||
([port data opts]))
|
||||
|
||||
(defn close
|
||||
"Closes a port.
|
||||
(Port/close port) ;=> true"
|
||||
[port])
|
||||
|
||||
(defn connect
|
||||
"Changes the owner of a port.
|
||||
(Port/connect port new-owner-pid)"
|
||||
[port pid])
|
||||
|
||||
(defn info
|
||||
"Returns information about a port.
|
||||
(Port/info port) ;=> [{:name ...} {:links [...]} ...]
|
||||
(Port/info port :name) ;=> port name"
|
||||
([port])
|
||||
([port item]))
|
||||
|
||||
(defn list
|
||||
"Returns all open ports.
|
||||
(Port/list) ;=> [#Port<0.5> ...]"
|
||||
[])
|
||||
|
||||
(defn monitor
|
||||
"Monitors a port. Returns reference."
|
||||
[type port])
|
||||
|
||||
(defn demonitor
|
||||
"Stops monitoring a port."
|
||||
([ref])
|
||||
([ref opts]))
|
||||
@@ -0,0 +1,166 @@
|
||||
(ns Process
|
||||
"Elixir Process module — BEAM process management.
|
||||
|
||||
In CljElixir: (Process/sleep 1000), (Process/send pid msg), etc.
|
||||
Provides utilities for working with BEAM processes (lightweight actors).")
|
||||
|
||||
(defn sleep
|
||||
"Sleeps the current process for `timeout` milliseconds or :infinity.
|
||||
(Process/sleep 1000) ;=> :ok (sleeps 1 second)
|
||||
(Process/sleep :infinity) ;=> blocks forever"
|
||||
[timeout])
|
||||
|
||||
(defn send
|
||||
"Sends `msg` to `dest` (pid, port, or registered name). Returns `msg`.
|
||||
(Process/send pid {:hello \"world\"})
|
||||
(Process/send pid msg [:noconnect]) ;=> with options"
|
||||
([dest msg])
|
||||
([dest msg opts]))
|
||||
|
||||
(defn spawn
|
||||
"Spawns a new process. Returns the PID.
|
||||
(Process/spawn (fn [] (IO/puts \"hi\")) [])
|
||||
(Process/spawn MyModule :my-func [arg1 arg2] [:link])"
|
||||
([fun opts])
|
||||
([module function args opts]))
|
||||
|
||||
(defn spawn-link
|
||||
"Spawns a linked process. If the child crashes, the parent crashes too.
|
||||
(Process/spawn-link (fn [] (IO/puts \"linked\")) [])"
|
||||
([fun opts])
|
||||
([module function args opts]))
|
||||
|
||||
(defn spawn-monitor
|
||||
"Spawns a monitored process. Returns {pid, ref}.
|
||||
(Process/spawn-monitor (fn [] (do-work)))"
|
||||
([fun])
|
||||
([module function args]))
|
||||
|
||||
(defn alive?
|
||||
"Returns true if the process identified by `pid` is alive.
|
||||
(Process/alive? pid) ;=> true"
|
||||
[pid])
|
||||
|
||||
(defn self
|
||||
"Returns the PID of the calling process.
|
||||
(Process/self) ;=> #PID<0.123.0>"
|
||||
[])
|
||||
|
||||
(defn whereis
|
||||
"Returns the PID registered under `name`, or nil.
|
||||
(Process/whereis :my-server) ;=> #PID<0.456.0>"
|
||||
[name])
|
||||
|
||||
(defn register
|
||||
"Registers a PID under the given atom `name`.
|
||||
(Process/register pid :my-server) ;=> true"
|
||||
[pid name])
|
||||
|
||||
(defn unregister
|
||||
"Unregisters the given `name`.
|
||||
(Process/unregister :my-server) ;=> true"
|
||||
[name])
|
||||
|
||||
(defn registered
|
||||
"Returns a list of all registered process names.
|
||||
(Process/registered) ;=> [:logger :code-server ...]"
|
||||
[])
|
||||
|
||||
(defn link
|
||||
"Creates a link between the calling process and `pid_or_port`.
|
||||
(Process/link pid)"
|
||||
[pid-or-port])
|
||||
|
||||
(defn unlink
|
||||
"Removes a link between the calling process and `pid_or_port`.
|
||||
(Process/unlink pid)"
|
||||
[pid-or-port])
|
||||
|
||||
(defn monitor
|
||||
"Starts monitoring `item`. Returns a reference.
|
||||
(Process/monitor :process pid)"
|
||||
([type item])
|
||||
([type item opts]))
|
||||
|
||||
(defn demonitor
|
||||
"Stops monitoring. The `ref` is from a previous `monitor` call.
|
||||
(Process/demonitor ref)
|
||||
(Process/demonitor ref [:flush])"
|
||||
([ref])
|
||||
([ref opts]))
|
||||
|
||||
(defn exit
|
||||
"Sends an exit signal to `pid`.
|
||||
(Process/exit pid :normal) ;=> normal shutdown
|
||||
(Process/exit pid :kill) ;=> forceful kill (untrappable)"
|
||||
[pid reason])
|
||||
|
||||
(defn flag
|
||||
"Sets process flags. Returns the old value.
|
||||
(Process/flag :trap-exit true) ;=> false (previous value)
|
||||
(Process/flag :priority :high)"
|
||||
([flag value])
|
||||
([pid flag value]))
|
||||
|
||||
(defn info
|
||||
"Returns info about a process.
|
||||
(Process/info pid) ;=> keyword list of process info
|
||||
(Process/info pid :message-queue-len) ;=> message queue length"
|
||||
([pid])
|
||||
([pid item]))
|
||||
|
||||
(defn list
|
||||
"Returns a list of all running process PIDs.
|
||||
(Process/list) ;=> [#PID<0.0.0> #PID<0.1.0> ...]"
|
||||
[])
|
||||
|
||||
(defn group-leader
|
||||
"Returns or sets the group leader.
|
||||
(Process/group-leader) ;=> #PID<0.64.0>
|
||||
(Process/group-leader pid gl)"
|
||||
([])
|
||||
([pid leader]))
|
||||
|
||||
(defn hibernate
|
||||
"Puts the process into hibernation (frees memory). Resumes in `fun`.
|
||||
(Process/hibernate Module :function [args])"
|
||||
[module function args])
|
||||
|
||||
(defn send-after
|
||||
"Sends `msg` to `dest` after `time` milliseconds. Returns a timer ref.
|
||||
(Process/send-after self :tick 1000)"
|
||||
([dest msg time])
|
||||
([dest msg time opts]))
|
||||
|
||||
(defn cancel-timer
|
||||
"Cancels a timer created by send-after."
|
||||
([timer-ref])
|
||||
([timer-ref opts]))
|
||||
|
||||
(defn read-timer
|
||||
"Returns the time remaining for a timer, or false."
|
||||
([timer-ref])
|
||||
([timer-ref opts]))
|
||||
|
||||
(defn put
|
||||
"Puts a key-value pair in the process dictionary. Returns old value or nil.
|
||||
(Process/put :my-key \"my value\") ;=> nil"
|
||||
[key value])
|
||||
|
||||
(defn get
|
||||
"Gets a value from the process dictionary.
|
||||
(Process/get :my-key) ;=> \"my value\"
|
||||
(Process/get :missing :default) ;=> :default"
|
||||
([key])
|
||||
([key default]))
|
||||
|
||||
(defn delete
|
||||
"Deletes a key from the process dictionary. Returns old value.
|
||||
(Process/delete :my-key) ;=> \"my value\""
|
||||
[key])
|
||||
|
||||
(defn get-keys
|
||||
"Returns all keys in the process dictionary (or keys with given value).
|
||||
(Process/get-keys) ;=> [:my-key ...]"
|
||||
([])
|
||||
([value]))
|
||||
@@ -0,0 +1,37 @@
|
||||
(ns Range
|
||||
"Elixir Range module — integer ranges.
|
||||
|
||||
In CljElixir: Ranges use first..last or first..last//step syntax.
|
||||
(Range/new 1 10) ;=> 1..10")
|
||||
|
||||
(defn new
|
||||
"Creates a new range.
|
||||
(Range/new 1 10) ;=> 1..10
|
||||
(Range/new 1 10 2) ;=> 1..10//2 (step of 2)"
|
||||
([first last])
|
||||
([first last step]))
|
||||
|
||||
(defn size
|
||||
"Returns the number of elements in the range.
|
||||
(Range/size (Range/new 1 10)) ;=> 10"
|
||||
[range])
|
||||
|
||||
(defn disjoint?
|
||||
"Returns true if two ranges don't overlap.
|
||||
(Range/disjoint? (Range/new 1 5) (Range/new 6 10)) ;=> true"
|
||||
[range1 range2])
|
||||
|
||||
(defn shift
|
||||
"Shifts a range by `steps`.
|
||||
(Range/shift (Range/new 1 5) 2) ;=> 3..7"
|
||||
[range steps])
|
||||
|
||||
(defn split
|
||||
"Splits a range at position `split`.
|
||||
(Range/split (Range/new 1 5) 3) ;=> {1..3 4..5}"
|
||||
[range split])
|
||||
|
||||
(defn to-list
|
||||
"Converts range to a list.
|
||||
(Range/to-list (Range/new 1 5)) ;=> [1 2 3 4 5]"
|
||||
[range])
|
||||
@@ -0,0 +1,85 @@
|
||||
(ns Regex
|
||||
"Elixir Regex module — regular expression operations (wraps Erlang :re).
|
||||
|
||||
In CljElixir: (Regex/match? ~r/pattern/ string), etc.
|
||||
Regex literals use ~r/pattern/flags syntax.")
|
||||
|
||||
(defn compile
|
||||
"Compiles a regex pattern string. Returns {:ok regex} or {:error reason}.
|
||||
(Regex/compile \"^hello\") ;=> {:ok ~r/^hello/}
|
||||
(Regex/compile \"hello\" \"i\") ;=> case-insensitive"
|
||||
([source])
|
||||
([source opts]))
|
||||
|
||||
(defn compile!
|
||||
"Compiles a regex. Raises on invalid pattern.
|
||||
(Regex/compile! \"^hello\") ;=> ~r/^hello/"
|
||||
([source])
|
||||
([source opts]))
|
||||
|
||||
(defn match?
|
||||
"Returns true if `string` matches `regex`.
|
||||
(Regex/match? ~r/\\d+/ \"hello123\") ;=> true"
|
||||
[regex string])
|
||||
|
||||
(defn run
|
||||
"Runs the regex against `string`. Returns list of matches or nil.
|
||||
(Regex/run ~r/(\\w+)@(\\w+)/ \"user@host\") ;=> [\"user@host\" \"user\" \"host\"]
|
||||
(Regex/run ~r/\\d+/ \"hello\") ;=> nil"
|
||||
([regex string])
|
||||
([regex string opts]))
|
||||
|
||||
(defn scan
|
||||
"Scans the string for all matches.
|
||||
(Regex/scan ~r/\\d+/ \"a1b2c3\") ;=> [[\"1\"] [\"2\"] [\"3\"]]"
|
||||
([regex string])
|
||||
([regex string opts]))
|
||||
|
||||
(defn named-captures
|
||||
"Returns a map of named captures.
|
||||
(Regex/named-captures ~r/(?P<year>\\d{4})-(?P<month>\\d{2})/ \"2024-03\")
|
||||
;=> %{\"year\" => \"2024\", \"month\" => \"03\"}"
|
||||
([regex string])
|
||||
([regex string opts]))
|
||||
|
||||
(defn replace
|
||||
"Replaces regex matches in `string` with `replacement`.
|
||||
(Regex/replace ~r/\\d+/ \"a1b2\" \"X\") ;=> \"aXbX\"
|
||||
(Regex/replace ~r/(\\w+)/ \"hello\" (fn [match] (String/upcase match)))"
|
||||
([regex string replacement])
|
||||
([regex string replacement opts]))
|
||||
|
||||
(defn split
|
||||
"Splits `string` by `regex`.
|
||||
(Regex/split ~r/\\s+/ \"hello world\") ;=> [\"hello\" \"world\"]
|
||||
(Regex/split ~r/,/ \"a,b,c\" :parts 2) ;=> [\"a\" \"b,c\"]"
|
||||
([regex string])
|
||||
([regex string opts]))
|
||||
|
||||
(defn source
|
||||
"Returns the source string of a compiled regex.
|
||||
(Regex/source ~r/hello/) ;=> \"hello\""
|
||||
[regex])
|
||||
|
||||
(defn opts
|
||||
"Returns the options string of a compiled regex.
|
||||
(Regex/opts ~r/hello/i) ;=> \"i\""
|
||||
[regex])
|
||||
|
||||
(defn re-pattern
|
||||
"Returns the underlying compiled pattern."
|
||||
[regex])
|
||||
|
||||
(defn names
|
||||
"Returns a list of named capture group names.
|
||||
(Regex/names ~r/(?P<name>\\w+)/) ;=> [\"name\"]"
|
||||
[regex])
|
||||
|
||||
(defn escape
|
||||
"Escapes a string for use in a regex.
|
||||
(Regex/escape \"hello.world\") ;=> \"hello\\\\.world\""
|
||||
[string])
|
||||
|
||||
(defn regex?
|
||||
"Returns true if `term` is a compiled regex."
|
||||
[term])
|
||||
@@ -0,0 +1,86 @@
|
||||
(ns Registry
|
||||
"Elixir Registry module — process registry for local name lookup.
|
||||
|
||||
In CljElixir: (Registry/start-link :keys :unique :name :my-registry), etc.
|
||||
Registries allow processes to be found by key (unique or duplicate).")
|
||||
|
||||
(defn start-link
|
||||
"Starts a registry. Returns {:ok pid}.
|
||||
(Registry/start-link :keys :unique :name MyApp.Registry)
|
||||
(Registry/start-link :keys :duplicate :name MyApp.PubSub)"
|
||||
[opts])
|
||||
|
||||
(defn register
|
||||
"Registers the current process under `key`.
|
||||
(Registry/register MyApp.Registry :my-key \"value\") ;=> {:ok pid}"
|
||||
[registry key value])
|
||||
|
||||
(defn unregister
|
||||
"Unregisters the current process from `key`.
|
||||
(Registry/unregister MyApp.Registry :my-key)"
|
||||
[registry key])
|
||||
|
||||
(defn lookup
|
||||
"Looks up processes registered under `key`. Returns [{pid, value} ...].
|
||||
(Registry/lookup MyApp.Registry :my-key) ;=> [{#PID<0.123.0> \"value\"}]"
|
||||
[registry key])
|
||||
|
||||
(defn dispatch
|
||||
"Dispatches to all registered processes for `key`.
|
||||
(Registry/dispatch MyApp.PubSub :topic (fn [{pid val}] (send pid :msg)))"
|
||||
([registry key fun])
|
||||
([registry key fun opts]))
|
||||
|
||||
(defn keys
|
||||
"Returns all keys registered by the current process.
|
||||
(Registry/keys MyApp.Registry (Process/self)) ;=> [:key1 :key2]"
|
||||
[registry pid])
|
||||
|
||||
(defn values
|
||||
"Returns all {pid, value} pairs for `key`.
|
||||
(Registry/values MyApp.Registry :my-key (Process/self))"
|
||||
[registry key pid])
|
||||
|
||||
(defn count
|
||||
"Returns the number of registered entries.
|
||||
(Registry/count MyApp.Registry) ;=> 5"
|
||||
[registry])
|
||||
|
||||
(defn count-match
|
||||
"Returns count of entries matching `key` and `pattern`.
|
||||
(Registry/count-match MyApp.Registry :my-key :_)"
|
||||
([registry key pattern])
|
||||
([registry key pattern guards]))
|
||||
|
||||
(defn match
|
||||
"Matches entries by key and value pattern.
|
||||
(Registry/match MyApp.Registry :my-key :_) ;=> all values for key"
|
||||
([registry key pattern])
|
||||
([registry key pattern guards]))
|
||||
|
||||
(defn select
|
||||
"Selects entries using match specifications."
|
||||
[registry spec])
|
||||
|
||||
(defn update-value
|
||||
"Updates the value for the current process's registration.
|
||||
(Registry/update-value MyApp.Registry :my-key (fn [old] (inc old)))"
|
||||
[registry key callback])
|
||||
|
||||
(defn unregister-match
|
||||
"Unregisters entries matching key and pattern."
|
||||
([registry key pattern])
|
||||
([registry key pattern guards]))
|
||||
|
||||
(defn meta
|
||||
"Gets metadata for a registry key.
|
||||
(Registry/meta MyApp.Registry :my-key)"
|
||||
[registry key])
|
||||
|
||||
(defn put-meta
|
||||
"Sets metadata for a registry key."
|
||||
[registry key value])
|
||||
|
||||
(defn child-spec
|
||||
"Returns a child spec for starting under a supervisor."
|
||||
[opts])
|
||||
@@ -0,0 +1,168 @@
|
||||
(ns Stream
|
||||
"Elixir Stream module — lazy, composable enumerables.
|
||||
|
||||
In CljElixir: (Stream/map coll f), (Stream/filter coll f), etc.
|
||||
Streams are lazy: operations are only executed when the stream is consumed
|
||||
(e.g., by Enum/to-list, Enum/take, etc.).")
|
||||
|
||||
(defn map
|
||||
"Lazily maps `fun` over `enumerable`.
|
||||
(-> [1 2 3] (Stream/map (fn [x] (* x 2))) (Enum/to-list)) ;=> [2 4 6]"
|
||||
[enumerable fun])
|
||||
|
||||
(defn filter
|
||||
"Lazily filters elements.
|
||||
(-> [1 2 3 4] (Stream/filter (fn [x] (> x 2))) (Enum/to-list)) ;=> [3 4]"
|
||||
[enumerable fun])
|
||||
|
||||
(defn reject
|
||||
"Lazily rejects elements where `fun` returns truthy."
|
||||
[enumerable fun])
|
||||
|
||||
(defn flat-map
|
||||
"Lazily maps and flattens."
|
||||
[enumerable fun])
|
||||
|
||||
(defn take
|
||||
"Lazily takes `count` elements.
|
||||
(-> (Stream/iterate 0 inc) (Stream/take 5) (Enum/to-list)) ;=> [0 1 2 3 4]"
|
||||
[enumerable count])
|
||||
|
||||
(defn take-while
|
||||
"Lazily takes while `fun` returns truthy."
|
||||
[enumerable fun])
|
||||
|
||||
(defn take-every
|
||||
"Lazily takes every `nth` element."
|
||||
[enumerable nth])
|
||||
|
||||
(defn drop
|
||||
"Lazily drops `count` elements."
|
||||
[enumerable count])
|
||||
|
||||
(defn drop-while
|
||||
"Lazily drops while `fun` returns truthy."
|
||||
[enumerable fun])
|
||||
|
||||
(defn chunk-by
|
||||
"Lazily chunks by result of `fun`."
|
||||
[enumerable fun])
|
||||
|
||||
(defn chunk-every
|
||||
"Lazily chunks into groups of `count`.
|
||||
(-> [1 2 3 4 5] (Stream/chunk-every 2) (Enum/to-list)) ;=> [[1 2] [3 4] [5]]"
|
||||
([enumerable count])
|
||||
([enumerable count step])
|
||||
([enumerable count step leftover]))
|
||||
|
||||
(defn chunk-while
|
||||
"Lazily chunks with custom accumulator logic."
|
||||
[enumerable acc chunk-fun after-fun])
|
||||
|
||||
(defn concat
|
||||
"Lazily concatenates enumerables.
|
||||
(-> (Stream/concat [1 2] [3 4]) Enum/to-list) ;=> [1 2 3 4]"
|
||||
([enumerables])
|
||||
([first rest]))
|
||||
|
||||
(defn dedup
|
||||
"Lazily removes consecutive duplicates."
|
||||
([enumerable]))
|
||||
|
||||
(defn dedup-by
|
||||
"Lazily removes consecutive duplicates by `fun`."
|
||||
[enumerable fun])
|
||||
|
||||
(defn each
|
||||
"Lazily invokes `fun` for side effects. Elements pass through.
|
||||
(-> [1 2 3] (Stream/each (fn [x] (IO/puts x))) Enum/to-list)"
|
||||
[enumerable fun])
|
||||
|
||||
(defn scan
|
||||
"Lazily emits successive reduced values.
|
||||
(-> [1 2 3 4] (Stream/scan 0 +) Enum/to-list) ;=> [1 3 6 10]"
|
||||
([enumerable fun])
|
||||
([enumerable acc fun]))
|
||||
|
||||
(defn transform
|
||||
"Lazily transforms with an accumulator."
|
||||
[enumerable acc reducer after-fun])
|
||||
|
||||
(defn uniq
|
||||
"Lazily removes duplicates."
|
||||
[enumerable])
|
||||
|
||||
(defn uniq-by
|
||||
"Lazily removes duplicates by `fun`."
|
||||
[enumerable fun])
|
||||
|
||||
(defn with-index
|
||||
"Lazily adds indices.
|
||||
(-> [:a :b :c] Stream/with-index Enum/to-list) ;=> [{:a 0} {:b 1} {:c 2}]"
|
||||
([enumerable])
|
||||
([enumerable offset]))
|
||||
|
||||
(defn zip
|
||||
"Lazily zips enumerables.
|
||||
(-> (Stream/zip [1 2 3] [:a :b :c]) Enum/to-list) ;=> [{1 :a} {2 :b} {3 :c}]"
|
||||
([enumerables])
|
||||
([enum1 enum2]))
|
||||
|
||||
(defn zip-with
|
||||
"Lazily zips with a merge function."
|
||||
([enumerables zip-fun])
|
||||
([enum1 enum2 zip-fun]))
|
||||
|
||||
;; --- Generators ---
|
||||
|
||||
(defn iterate
|
||||
"Generates an infinite stream by repeatedly applying `fun`.
|
||||
(-> (Stream/iterate 0 inc) (Stream/take 5) Enum/to-list) ;=> [0 1 2 3 4]"
|
||||
[start-value next-fun])
|
||||
|
||||
(defn repeatedly
|
||||
"Generates a stream by calling `fun` repeatedly.
|
||||
(-> (Stream/repeatedly (fn [] (rand 10))) (Stream/take 3) Enum/to-list)"
|
||||
([fun])
|
||||
([count fun]))
|
||||
|
||||
(defn unfold
|
||||
"Generates a stream with an accumulator. `fun` returns {emit, next-acc} or nil.
|
||||
(-> (Stream/unfold 5 (fn [n] (if (> n 0) {n (- n 1)} nil)))
|
||||
Enum/to-list) ;=> [5 4 3 2 1]"
|
||||
[acc fun])
|
||||
|
||||
(defn resource
|
||||
"Creates a stream with setup/cleanup. Useful for external resources.
|
||||
(Stream/resource
|
||||
(fn [] (File/open! \"file.txt\"))
|
||||
(fn [file] ...)
|
||||
(fn [file] (File/close file)))"
|
||||
[start-fun next-fun after-fun])
|
||||
|
||||
(defn cycle
|
||||
"Repeats an enumerable infinitely.
|
||||
(-> (Stream/cycle [1 2 3]) (Stream/take 7) Enum/to-list) ;=> [1 2 3 1 2 3 1]"
|
||||
[enumerable])
|
||||
|
||||
(defn interval
|
||||
"Emits incrementing integers at `interval` milliseconds.
|
||||
(-> (Stream/interval 1000) (Stream/take 3) Enum/to-list) ;=> [0 1 2] (1s apart)"
|
||||
[interval])
|
||||
|
||||
(defn timer
|
||||
"Emits a single value of 0 after `delay` milliseconds.
|
||||
(-> (Stream/timer 1000) Enum/to-list) ;=> [0]"
|
||||
[delay])
|
||||
|
||||
(defn run
|
||||
"Consumes the stream for side effects. Returns :ok."
|
||||
[stream])
|
||||
|
||||
(defn intersperse
|
||||
"Lazily intersperses `separator` between elements."
|
||||
[enumerable separator])
|
||||
|
||||
(defn map-every
|
||||
"Lazily maps `fun` over every `nth` element."
|
||||
[enumerable nth fun])
|
||||
@@ -0,0 +1,242 @@
|
||||
(ns String
|
||||
"Elixir String module — UTF-8 string operations.
|
||||
|
||||
In CljElixir: (String/split s \" \"), (String/trim s), etc.
|
||||
Strings in Elixir are UTF-8 encoded binaries.")
|
||||
|
||||
;; --- Searching ---
|
||||
|
||||
(defn contains?
|
||||
"Returns true if `string` contains `pattern`.
|
||||
(String/contains? \"hello world\" \"world\") ;=> true
|
||||
(String/contains? \"hello\" [\"x\" \"e\"]) ;=> true (any match)"
|
||||
[string pattern])
|
||||
|
||||
(defn starts-with?
|
||||
"Returns true if `string` starts with `prefix`.
|
||||
(String/starts-with? \"hello\" \"he\") ;=> true"
|
||||
[string prefix])
|
||||
|
||||
(defn ends-with?
|
||||
"Returns true if `string` ends with `suffix`.
|
||||
(String/ends-with? \"hello\" \"lo\") ;=> true"
|
||||
[string suffix])
|
||||
|
||||
(defn match?
|
||||
"Returns true if `string` matches the regex `pattern`.
|
||||
(String/match? \"hello123\" ~r/\\d+/) ;=> true"
|
||||
[string pattern])
|
||||
|
||||
;; --- Splitting & Joining ---
|
||||
|
||||
(defn split
|
||||
"Splits `string` by `pattern`. Without a pattern splits on whitespace.
|
||||
(String/split \"a,b,c\" \",\") ;=> [\"a\" \"b\" \"c\"]
|
||||
(String/split \"a,b,c\" \",\" 2) ;=> [\"a\" \"b,c\"]"
|
||||
([string])
|
||||
([string pattern])
|
||||
([string pattern parts]))
|
||||
|
||||
(defn split-at
|
||||
"Splits `string` at `position`.
|
||||
(String/split-at \"hello\" 3) ;=> {\"hel\" \"lo\"}"
|
||||
[string position])
|
||||
|
||||
;; --- Transformation ---
|
||||
|
||||
(defn replace
|
||||
"Replaces occurrences of `pattern` in `string` with `replacement`.
|
||||
(String/replace \"hello world\" \"world\" \"elixir\") ;=> \"hello elixir\"
|
||||
(String/replace \"aabba\" ~r/a/ \"x\") ;=> \"xxbbx\""
|
||||
[string pattern replacement])
|
||||
|
||||
(defn replace-prefix
|
||||
"Replaces prefix if it matches.
|
||||
(String/replace-prefix \"hello\" \"he\" \"HE\") ;=> \"HEllo\""
|
||||
[string match replacement])
|
||||
|
||||
(defn replace-suffix
|
||||
"Replaces suffix if it matches.
|
||||
(String/replace-suffix \"hello\" \"lo\" \"LO\") ;=> \"helLO\""
|
||||
[string match replacement])
|
||||
|
||||
(defn replace-leading
|
||||
"Replaces all leading occurrences of `match` with `replacement`."
|
||||
[string match replacement])
|
||||
|
||||
(defn replace-trailing
|
||||
"Replaces all trailing occurrences of `match` with `replacement`."
|
||||
[string match replacement])
|
||||
|
||||
(defn upcase
|
||||
"Converts string to uppercase.
|
||||
(String/upcase \"hello\") ;=> \"HELLO\""
|
||||
[string])
|
||||
|
||||
(defn downcase
|
||||
"Converts string to lowercase.
|
||||
(String/downcase \"HELLO\") ;=> \"hello\""
|
||||
[string])
|
||||
|
||||
(defn capitalize
|
||||
"Capitalizes the first character, downcases the rest.
|
||||
(String/capitalize \"hello world\") ;=> \"Hello world\""
|
||||
[string])
|
||||
|
||||
(defn reverse
|
||||
"Reverses the string (grapheme-aware for Unicode).
|
||||
(String/reverse \"hello\") ;=> \"olleh\""
|
||||
[string])
|
||||
|
||||
(defn duplicate
|
||||
"Repeats `string` `n` times.
|
||||
(String/duplicate \"ha\" 3) ;=> \"hahaha\""
|
||||
[string n])
|
||||
|
||||
(defn pad-leading
|
||||
"Pads `string` on the left to `count` characters.
|
||||
(String/pad-leading \"13\" 5 \"0\") ;=> \"00013\""
|
||||
([string count])
|
||||
([string count padding]))
|
||||
|
||||
(defn pad-trailing
|
||||
"Pads `string` on the right to `count` characters.
|
||||
(String/pad-trailing \"hi\" 5) ;=> \"hi \""
|
||||
([string count])
|
||||
([string count padding]))
|
||||
|
||||
;; --- Trimming ---
|
||||
|
||||
(defn trim
|
||||
"Removes leading and trailing whitespace (or specified characters).
|
||||
(String/trim \" hello \") ;=> \"hello\""
|
||||
([string])
|
||||
([string to-trim]))
|
||||
|
||||
(defn trim-leading
|
||||
"Removes leading whitespace.
|
||||
(String/trim-leading \" hello\") ;=> \"hello\""
|
||||
([string])
|
||||
([string to-trim]))
|
||||
|
||||
(defn trim-trailing
|
||||
"Removes trailing whitespace.
|
||||
(String/trim-trailing \"hello \") ;=> \"hello\""
|
||||
([string])
|
||||
([string to-trim]))
|
||||
|
||||
;; --- Slicing & Access ---
|
||||
|
||||
(defn slice
|
||||
"Returns a substring starting at `start` for `length` characters.
|
||||
(String/slice \"hello\" 1 3) ;=> \"ell\"
|
||||
(String/slice \"hello\" 1..3) ;=> \"ell\""
|
||||
([string range])
|
||||
([string start length]))
|
||||
|
||||
(defn at
|
||||
"Returns the grapheme at `position`. Negative indices count from end.
|
||||
(String/at \"hello\" 1) ;=> \"e\"
|
||||
(String/at \"hello\" -1) ;=> \"o\""
|
||||
[string position])
|
||||
|
||||
(defn first
|
||||
"Returns the first grapheme.
|
||||
(String/first \"hello\") ;=> \"h\""
|
||||
[string])
|
||||
|
||||
(defn last
|
||||
"Returns the last grapheme.
|
||||
(String/last \"hello\") ;=> \"o\""
|
||||
[string])
|
||||
|
||||
(defn length
|
||||
"Returns the number of Unicode graphemes.
|
||||
(String/length \"héllo\") ;=> 5"
|
||||
[string])
|
||||
|
||||
(defn byte-size
|
||||
"Returns the number of bytes in the string.
|
||||
(String/byte-size \"héllo\") ;=> 6 (é is 2 bytes in UTF-8)"
|
||||
[string])
|
||||
|
||||
(defn graphemes
|
||||
"Returns a list of grapheme clusters.
|
||||
(String/graphemes \"hello\") ;=> [\"h\" \"e\" \"l\" \"l\" \"o\"]"
|
||||
[string])
|
||||
|
||||
(defn codepoints
|
||||
"Returns a list of codepoints.
|
||||
(String/codepoints \"hello\") ;=> [\"h\" \"e\" \"l\" \"l\" \"o\"]"
|
||||
[string])
|
||||
|
||||
(defn next-grapheme
|
||||
"Returns tuple {grapheme rest} or nil.
|
||||
(String/next-grapheme \"abc\") ;=> {\"a\" \"bc\"}"
|
||||
[string])
|
||||
|
||||
(defn next-codepoint
|
||||
"Returns tuple {codepoint rest} or nil."
|
||||
[string])
|
||||
|
||||
;; --- Conversion ---
|
||||
|
||||
(defn to-integer
|
||||
"Converts string to integer.
|
||||
(String/to-integer \"123\") ;=> 123
|
||||
(String/to-integer \"FF\" 16) ;=> 255"
|
||||
([string])
|
||||
([string base]))
|
||||
|
||||
(defn to-float
|
||||
"Converts string to float.
|
||||
(String/to-float \"3.14\") ;=> 3.14"
|
||||
[string])
|
||||
|
||||
(defn to-atom
|
||||
"Converts string to an existing atom.
|
||||
(String/to-atom \"hello\") ;=> :hello"
|
||||
[string])
|
||||
|
||||
(defn to-existing-atom
|
||||
"Converts string to an existing atom. Raises if atom doesn't exist.
|
||||
(String/to-existing-atom \"hello\") ;=> :hello"
|
||||
[string])
|
||||
|
||||
(defn to-charlist
|
||||
"Converts string to a charlist.
|
||||
(String/to-charlist \"hello\") ;=> 'hello'"
|
||||
[string])
|
||||
|
||||
(defn myers-difference
|
||||
"Returns a keyword list of edit steps to transform string1 into string2.
|
||||
(String/myers-difference \"abc\" \"adc\") ;=> [[:eq \"a\"] [:del \"b\"] [:ins \"d\"] [:eq \"c\"]]"
|
||||
[string1 string2])
|
||||
|
||||
(defn valid?
|
||||
"Returns true if `string` is a valid UTF-8 string.
|
||||
(String/valid? \"hello\") ;=> true"
|
||||
[string])
|
||||
|
||||
(defn printable?
|
||||
"Returns true if `string` consists only of printable characters.
|
||||
(String/printable? \"hello\") ;=> true"
|
||||
[string])
|
||||
|
||||
(defn equivalent?
|
||||
"Returns true if two strings are equivalent ignoring Unicode normalization differences."
|
||||
[string1 string2])
|
||||
|
||||
(defn bag-distance
|
||||
"Returns the bag distance between two strings (simple edit distance metric)."
|
||||
[string1 string2])
|
||||
|
||||
(defn jaro-distance
|
||||
"Returns the Jaro distance between two strings (0.0 to 1.0).
|
||||
(String/jaro-distance \"Dwayne\" \"Duane\") ;=> 0.822..."
|
||||
[string1 string2])
|
||||
|
||||
(defn chunk
|
||||
"Splits string into chunks by character type.
|
||||
(String/chunk \"abc123def\" :valid) ;=> [\"abc123def\"]"
|
||||
[string mode])
|
||||
@@ -0,0 +1,57 @@
|
||||
(ns Supervisor
|
||||
"Elixir Supervisor module — OTP supervisor for fault-tolerant process trees.
|
||||
|
||||
In CljElixir: (Supervisor/start-link children opts), etc.
|
||||
Strategies: :one-for-one, :one-for-all, :rest-for-one.")
|
||||
|
||||
(defn start-link
|
||||
"Starts a supervisor with child specifications.
|
||||
(Supervisor/start-link [{MyWorker [arg1]}] :strategy :one-for-one)
|
||||
(Supervisor/start-link MyApp [init-arg])"
|
||||
([children-or-module opts-or-arg])
|
||||
([module arg opts]))
|
||||
|
||||
(defn start-child
|
||||
"Dynamically starts a child under the supervisor.
|
||||
(Supervisor/start-child sup child-spec)"
|
||||
[supervisor child-spec])
|
||||
|
||||
(defn terminate-child
|
||||
"Terminates a child process.
|
||||
(Supervisor/terminate-child sup child-id)"
|
||||
[supervisor child-id])
|
||||
|
||||
(defn restart-child
|
||||
"Restarts a terminated child.
|
||||
(Supervisor/restart-child sup child-id)"
|
||||
[supervisor child-id])
|
||||
|
||||
(defn delete-child
|
||||
"Deletes a terminated child specification.
|
||||
(Supervisor/delete-child sup child-id)"
|
||||
[supervisor child-id])
|
||||
|
||||
(defn which-children
|
||||
"Returns a list of all children with their info.
|
||||
(Supervisor/which-children sup) ;=> [{id, pid, type, modules} ...]"
|
||||
[supervisor])
|
||||
|
||||
(defn count-children
|
||||
"Returns a map with child counts by status.
|
||||
(Supervisor/count-children sup) ;=> %{active: 2, specs: 2, supervisors: 0, workers: 2}"
|
||||
[supervisor])
|
||||
|
||||
(defn stop
|
||||
"Stops the supervisor.
|
||||
(Supervisor/stop sup) ;=> :ok"
|
||||
([supervisor])
|
||||
([supervisor reason])
|
||||
([supervisor reason timeout]))
|
||||
|
||||
(defn child-spec
|
||||
"Builds a child specification map."
|
||||
[module overrides])
|
||||
|
||||
(defn init
|
||||
"Returns supervisor init spec. For use in module-based supervisors."
|
||||
[children-and-opts])
|
||||
@@ -0,0 +1,176 @@
|
||||
(ns System
|
||||
"Elixir System module — system-level information and operations.
|
||||
|
||||
In CljElixir: (System/argv), (System/halt 0), etc.")
|
||||
|
||||
(defn argv
|
||||
"Returns command line arguments as a list of strings.
|
||||
(System/argv) ;=> [\"--flag\" \"value\"]"
|
||||
[])
|
||||
|
||||
(defn halt
|
||||
"Halts the Erlang runtime. `status` is 0 (success) or positive integer.
|
||||
(System/halt 0) ;=> exits with code 0
|
||||
(System/halt 1) ;=> exits with code 1
|
||||
(System/halt \"crash message\") ;=> prints message and exits"
|
||||
([])
|
||||
([status]))
|
||||
|
||||
(defn stop
|
||||
"Gracefully stops the system. Runs all shutdown hooks.
|
||||
(System/stop 0)"
|
||||
([status]))
|
||||
|
||||
(defn cmd
|
||||
"Runs an external command. Returns {output, exit_status}.
|
||||
(System/cmd \"echo\" [\"hello\"]) ;=> {\"hello\\n\" 0}
|
||||
(System/cmd \"ls\" [\"-la\"] :cd \"/tmp\") ;=> with options"
|
||||
([command args])
|
||||
([command args opts]))
|
||||
|
||||
(defn shell
|
||||
"Runs a shell command. Returns {output, exit_status}.
|
||||
(System/shell \"echo hello && echo world\") ;=> {\"hello\\nworld\\n\" 0}"
|
||||
([command])
|
||||
([command opts]))
|
||||
|
||||
(defn get-env
|
||||
"Gets an environment variable. Returns nil if not set.
|
||||
(System/get-env \"HOME\") ;=> \"/Users/ajet\"
|
||||
(System/get-env \"MISSING\" \"default\") ;=> \"default\"
|
||||
(System/get-env) ;=> all env vars as a map"
|
||||
([])
|
||||
([varname])
|
||||
([varname default]))
|
||||
|
||||
(defn put-env
|
||||
"Sets an environment variable.
|
||||
(System/put-env \"MY_VAR\" \"value\") ;=> :ok
|
||||
(System/put-env %{\"KEY1\" \"val1\" \"KEY2\" \"val2\"}) ;=> :ok"
|
||||
([varname value])
|
||||
([env-map]))
|
||||
|
||||
(defn delete-env
|
||||
"Deletes an environment variable.
|
||||
(System/delete-env \"MY_VAR\") ;=> :ok"
|
||||
[varname])
|
||||
|
||||
(defn fetch-env
|
||||
"Fetches an env var. Returns {:ok value} or :error.
|
||||
(System/fetch-env \"HOME\") ;=> {:ok \"/Users/ajet\"}"
|
||||
[varname])
|
||||
|
||||
(defn fetch-env!
|
||||
"Fetches an env var. Raises if not set.
|
||||
(System/fetch-env! \"HOME\") ;=> \"/Users/ajet\""
|
||||
[varname])
|
||||
|
||||
(defn cwd
|
||||
"Returns the current working directory.
|
||||
(System/cwd) ;=> \"/Users/ajet/repos/clje\""
|
||||
[])
|
||||
|
||||
(defn cwd!
|
||||
"Returns the current working directory. Raises on error."
|
||||
[])
|
||||
|
||||
(defn tmp-dir
|
||||
"Returns the system temporary directory.
|
||||
(System/tmp-dir) ;=> \"/tmp\""
|
||||
[])
|
||||
|
||||
(defn tmp-dir!
|
||||
"Returns the system temporary directory. Raises on error."
|
||||
[])
|
||||
|
||||
(defn user-home
|
||||
"Returns the user's home directory.
|
||||
(System/user-home) ;=> \"/Users/ajet\""
|
||||
[])
|
||||
|
||||
(defn user-home!
|
||||
"Returns the user's home directory. Raises on error."
|
||||
[])
|
||||
|
||||
(defn monotonic-time
|
||||
"Returns monotonic time. Useful for measuring elapsed time.
|
||||
(System/monotonic-time) ;=> nanoseconds
|
||||
(System/monotonic-time :millisecond) ;=> milliseconds"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn system-time
|
||||
"Returns system time (wall clock, not monotonic).
|
||||
(System/system-time) ;=> nanoseconds
|
||||
(System/system-time :second) ;=> seconds since epoch"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn os-time
|
||||
"Returns OS time.
|
||||
(System/os-time :second) ;=> seconds since epoch"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn unique-integer
|
||||
"Returns a unique integer.
|
||||
(System/unique-integer) ;=> -576460752303423485
|
||||
(System/unique-integer [:positive :monotonic]) ;=> 1"
|
||||
([])
|
||||
([modifiers]))
|
||||
|
||||
(defn version
|
||||
"Returns the Elixir version string.
|
||||
(System/version) ;=> \"1.19.5\""
|
||||
[])
|
||||
|
||||
(defn otp-release
|
||||
"Returns the OTP release number as a string.
|
||||
(System/otp-release) ;=> \"28\""
|
||||
[])
|
||||
|
||||
(defn build-info
|
||||
"Returns a map with build info."
|
||||
[])
|
||||
|
||||
(defn schedulers
|
||||
"Returns the number of schedulers.
|
||||
(System/schedulers) ;=> 8"
|
||||
[])
|
||||
|
||||
(defn schedulers-online
|
||||
"Returns the number of online schedulers.
|
||||
(System/schedulers-online) ;=> 8"
|
||||
[])
|
||||
|
||||
(defn pid
|
||||
"Returns the PID of the OS process running the VM.
|
||||
(System/pid) ;=> \"12345\""
|
||||
[])
|
||||
|
||||
(defn at-exit
|
||||
"Registers a function to run at VM exit.
|
||||
(System/at-exit (fn [status] (IO/puts \"bye\")))"
|
||||
[fun])
|
||||
|
||||
(defn stacktrace
|
||||
"Returns the last exception stacktrace for the calling process."
|
||||
[])
|
||||
|
||||
(defn no-halt
|
||||
"Configures whether the system halts on script completion."
|
||||
([status]))
|
||||
|
||||
(defn trap-signal
|
||||
"Traps an OS signal. Returns {:ok fun} or {:already-registered fun}.
|
||||
(System/trap-signal :sigterm (fn [] (System/stop 0)))"
|
||||
[signal fun])
|
||||
|
||||
(defn untrap-signal
|
||||
"Removes a previously registered signal trap."
|
||||
[signal id])
|
||||
|
||||
(defn compiled-endianness
|
||||
"Returns the endianness (:big or :little).
|
||||
(System/compiled-endianness) ;=> :little"
|
||||
[])
|
||||
@@ -0,0 +1,94 @@
|
||||
(ns Task
|
||||
"Elixir Task module — convenient process abstraction for async computation.
|
||||
|
||||
In CljElixir: (Task/async (fn [] (expensive-work))), etc.
|
||||
Tasks are processes meant to run a single action and return a result.")
|
||||
|
||||
(defn async
|
||||
"Starts a task linked to the caller. Returns a Task struct.
|
||||
(let [t (Task/async (fn [] (compute-result)))]
|
||||
(Task/await t)) ;=> result"
|
||||
([fun])
|
||||
([module function-name args]))
|
||||
|
||||
(defn await
|
||||
"Awaits a task reply. Raises on timeout (default 5000ms).
|
||||
(Task/await task) ;=> result
|
||||
(Task/await task 10000) ;=> with 10s timeout"
|
||||
([task])
|
||||
([task timeout]))
|
||||
|
||||
(defn await-many
|
||||
"Awaits multiple tasks. Returns a list of results.
|
||||
(Task/await-many [task1 task2 task3]) ;=> [result1 result2 result3]
|
||||
(Task/await-many tasks 10000) ;=> with 10s timeout"
|
||||
([tasks])
|
||||
([tasks timeout]))
|
||||
|
||||
(defn yield
|
||||
"Temporarily yields for a reply. Returns {:ok result}, {:exit reason}, or nil.
|
||||
(Task/yield task 5000) ;=> {:ok result} or nil (if still running)"
|
||||
([task])
|
||||
([task timeout]))
|
||||
|
||||
(defn yield-many
|
||||
"Yields on multiple tasks. Returns [{:ok result} | {:exit reason} | nil ...].
|
||||
(Task/yield-many [task1 task2] 5000)"
|
||||
([tasks])
|
||||
([tasks opts-or-timeout]))
|
||||
|
||||
(defn start
|
||||
"Starts a task that is not linked. Useful for fire-and-forget.
|
||||
(Task/start (fn [] (send-email)))"
|
||||
([fun])
|
||||
([module function-name args]))
|
||||
|
||||
(defn start-link
|
||||
"Starts a linked task. Useful under a supervisor.
|
||||
(Task/start-link (fn [] (do-work)))"
|
||||
([fun])
|
||||
([module function-name args]))
|
||||
|
||||
(defn shutdown
|
||||
"Shuts down a task. Returns {:ok reply}, {:exit reason}, or nil.
|
||||
(Task/shutdown task) ;=> {:ok result}
|
||||
(Task/shutdown task :brutal-kill) ;=> force shutdown"
|
||||
([task])
|
||||
([task timeout-or-brutal]))
|
||||
|
||||
(defn ignore
|
||||
"Ignores an existing task. The task continues but its result is discarded.
|
||||
(Task/ignore task)"
|
||||
[task])
|
||||
|
||||
(defn completed
|
||||
"Creates an already-completed task with the given `result`.
|
||||
(Task/completed {:ok \"cached\"}) ;=> a task that immediately resolves"
|
||||
[result])
|
||||
|
||||
(defn async-stream
|
||||
"Returns a stream that runs the given function concurrently on each element.
|
||||
(Task/async-stream [1 2 3] (fn [x] (expensive x)) :max-concurrency 4)"
|
||||
([enumerable fun])
|
||||
([enumerable fun opts])
|
||||
([enumerable module function-name opts]))
|
||||
|
||||
(defn Supervisor.start-link
|
||||
"Starts a Task.Supervisor. Use with async_nolink for fault-tolerant tasks."
|
||||
([opts]))
|
||||
|
||||
(defn Supervisor.async
|
||||
"Starts an async task under a Task.Supervisor.
|
||||
(Task/Supervisor.async supervisor (fn [] (do-work)))"
|
||||
([supervisor fun])
|
||||
([supervisor fun opts])
|
||||
([supervisor module fun args])
|
||||
([supervisor module fun args opts]))
|
||||
|
||||
(defn Supervisor.async-nolink
|
||||
"Like Supervisor.async but doesn't link. Safe for untrusted work.
|
||||
(Task/Supervisor.async-nolink supervisor (fn [] (risky-work)))"
|
||||
([supervisor fun])
|
||||
([supervisor fun opts])
|
||||
([supervisor module fun args])
|
||||
([supervisor module fun args opts]))
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
(ns Time
|
||||
"Elixir Time module — time-of-day operations.
|
||||
|
||||
In CljElixir: (Time/utc-now), (Time/new 12 30 0), etc.")
|
||||
|
||||
(defn utc-now
|
||||
"Returns the current UTC time.
|
||||
(Time/utc-now) ;=> ~T[12:30:00]"
|
||||
([])
|
||||
([calendar]))
|
||||
|
||||
(defn new
|
||||
"Creates a new time.
|
||||
(Time/new 12 30 0) ;=> {:ok ~T[12:30:00]}"
|
||||
([hour minute second])
|
||||
([hour minute second microsecond])
|
||||
([hour minute second microsecond calendar]))
|
||||
|
||||
(defn new!
|
||||
"Creates a new time. Raises on error.
|
||||
(Time/new! 12 30 0) ;=> ~T[12:30:00]"
|
||||
([hour minute second])
|
||||
([hour minute second microsecond])
|
||||
([hour minute second microsecond calendar]))
|
||||
|
||||
(defn from-iso8601
|
||||
"Parses ISO 8601 time. Returns {:ok time} or {:error reason}.
|
||||
(Time/from-iso8601 \"12:30:00\") ;=> {:ok ~T[12:30:00]}"
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn from-iso8601!
|
||||
"Parses ISO 8601 time. Raises on error."
|
||||
([string])
|
||||
([string calendar]))
|
||||
|
||||
(defn to-iso8601
|
||||
"Converts to ISO 8601 string.
|
||||
(Time/to-iso8601 time) ;=> \"12:30:00\""
|
||||
([time])
|
||||
([time format]))
|
||||
|
||||
(defn to-string
|
||||
"Converts to string.
|
||||
(Time/to-string time) ;=> \"12:30:00\""
|
||||
[time])
|
||||
|
||||
(defn add
|
||||
"Adds `amount` of time.
|
||||
(Time/add time 3600) ;=> +1 hour
|
||||
(Time/add time 30 :minute)"
|
||||
([time amount])
|
||||
([time amount unit]))
|
||||
|
||||
(defn diff
|
||||
"Returns difference between two times.
|
||||
(Time/diff t1 t2) ;=> seconds"
|
||||
([time1 time2])
|
||||
([time1 time2 unit]))
|
||||
|
||||
(defn truncate
|
||||
"Truncates to given precision.
|
||||
(Time/truncate time :second)"
|
||||
[time precision])
|
||||
|
||||
(defn compare
|
||||
"Compares two times. Returns :lt, :eq, or :gt."
|
||||
[time1 time2])
|
||||
|
||||
(defn before?
|
||||
"Returns true if `time1` is before `time2`."
|
||||
[time1 time2])
|
||||
|
||||
(defn after?
|
||||
"Returns true if `time1` is after `time2`."
|
||||
[time1 time2])
|
||||
|
||||
(defn shift
|
||||
"Shifts time by a duration.
|
||||
(Time/shift time :hour 1 :minute -15)"
|
||||
[time duration])
|
||||
|
||||
(defn from-erl
|
||||
"Converts Erlang time tuple.
|
||||
(Time/from-erl {12 30 0}) ;=> {:ok ~T[12:30:00]}"
|
||||
([tuple])
|
||||
([tuple microsecond])
|
||||
([tuple microsecond calendar]))
|
||||
|
||||
(defn from-erl!
|
||||
"Converts Erlang time tuple. Raises on error."
|
||||
([tuple])
|
||||
([tuple microsecond])
|
||||
([tuple microsecond calendar]))
|
||||
|
||||
(defn to-erl
|
||||
"Converts to Erlang time tuple.
|
||||
(Time/to-erl time) ;=> {12 30 0}"
|
||||
[time])
|
||||
|
||||
(defn from-seconds-after-midnight
|
||||
"Creates time from seconds after midnight.
|
||||
(Time/from-seconds-after-midnight 45000) ;=> {:ok ~T[12:30:00]}"
|
||||
([seconds])
|
||||
([seconds microsecond])
|
||||
([seconds microsecond calendar]))
|
||||
|
||||
(defn to-seconds-after-midnight
|
||||
"Returns seconds since midnight.
|
||||
(Time/to-seconds-after-midnight time) ;=> 45000"
|
||||
[time])
|
||||
@@ -0,0 +1,39 @@
|
||||
(ns Tuple
|
||||
"Elixir Tuple module — operations on tuples.
|
||||
|
||||
In CljElixir: (Tuple/to-list tup), (Tuple/append tup elem), etc.
|
||||
Tuples are fixed-size, contiguous containers on the BEAM.
|
||||
Create tuples with #el[...] syntax in CljElixir.")
|
||||
|
||||
(defn to-list
|
||||
"Converts a tuple to a list.
|
||||
(Tuple/to-list #el[1 2 3]) ;=> [1 2 3]"
|
||||
[tuple])
|
||||
|
||||
(defn append
|
||||
"Appends `value` to `tuple`.
|
||||
(Tuple/append #el[1 2] 3) ;=> #el[1 2 3]"
|
||||
[tuple value])
|
||||
|
||||
(defn insert-at
|
||||
"Inserts `value` at `index` in `tuple`.
|
||||
(Tuple/insert-at #el[1 2 3] 1 :a) ;=> #el[1 :a 2 3]"
|
||||
[tuple index value])
|
||||
|
||||
(defn delete-at
|
||||
"Deletes element at `index` from `tuple`.
|
||||
(Tuple/delete-at #el[1 2 3] 1) ;=> #el[1 3]"
|
||||
[tuple index])
|
||||
|
||||
(defn duplicate
|
||||
"Creates a tuple with `data` repeated `size` times.
|
||||
(Tuple/duplicate :ok 3) ;=> #el[:ok :ok :ok]"
|
||||
[data size])
|
||||
|
||||
(defn product
|
||||
"Returns the product of all numeric elements in the tuple."
|
||||
[tuple])
|
||||
|
||||
(defn sum
|
||||
"Returns the sum of all numeric elements in the tuple."
|
||||
[tuple])
|
||||
@@ -0,0 +1,101 @@
|
||||
(ns binary
|
||||
"Erlang :binary module — binary data operations.
|
||||
|
||||
In CljElixir: (binary/split data pattern), (binary/part data pos len), etc.
|
||||
Efficient binary search, split, and manipulation.")
|
||||
|
||||
(defn split
|
||||
"Splits binary by pattern. Returns list of parts.
|
||||
(binary/split \"a,b,c\" \",\") ;=> [\"a\" \"b,c\"]
|
||||
(binary/split \"a,b,c\" \",\" [:global]) ;=> [\"a\" \"b\" \"c\"]"
|
||||
([subject pattern])
|
||||
([subject pattern opts]))
|
||||
|
||||
(defn part
|
||||
"Extracts a part of a binary.
|
||||
(binary/part \"hello\" 1 3) ;=> \"ell\"
|
||||
(binary/part \"hello\" {1 3}) ;=> \"ell\""
|
||||
([subject pos-len])
|
||||
([subject pos len]))
|
||||
|
||||
(defn match
|
||||
"Finds the first/all occurrences of pattern in binary.
|
||||
(binary/match \"hello world\" \"world\") ;=> {6 5}
|
||||
(binary/match \"abcabc\" \"a\" [:global]) ;=> [... all positions ...]"
|
||||
([subject pattern])
|
||||
([subject pattern opts]))
|
||||
|
||||
(defn matches
|
||||
"Returns all matches (like match with :global).
|
||||
(binary/matches \"abcabc\" \"a\") ;=> [{0 1} {3 1}]"
|
||||
([subject pattern])
|
||||
([subject pattern opts]))
|
||||
|
||||
(defn replace
|
||||
"Replaces pattern in binary.
|
||||
(binary/replace \"hello\" \"l\" \"L\" [:global]) ;=> \"heLLo\""
|
||||
([subject pattern replacement])
|
||||
([subject pattern replacement opts]))
|
||||
|
||||
(defn at
|
||||
"Returns the byte at position.
|
||||
(binary/at \"hello\" 0) ;=> 104"
|
||||
[subject position])
|
||||
|
||||
(defn first
|
||||
"Returns the first byte.
|
||||
(binary/first \"hello\") ;=> 104"
|
||||
[subject])
|
||||
|
||||
(defn last
|
||||
"Returns the last byte.
|
||||
(binary/last \"hello\") ;=> 111"
|
||||
[subject])
|
||||
|
||||
(defn bin-to-list
|
||||
"Converts binary to list of bytes.
|
||||
(binary/bin-to-list \"hello\") ;=> [104 101 108 108 111]"
|
||||
([subject])
|
||||
([subject pos-len])
|
||||
([subject pos len]))
|
||||
|
||||
(defn list-to-bin
|
||||
"Converts byte list to binary.
|
||||
(binary/list-to-bin [104 101 108 108 111]) ;=> \"hello\""
|
||||
[byte-list])
|
||||
|
||||
(defn copy
|
||||
"Copies a binary, optionally repeating it.
|
||||
(binary/copy \"ab\" 3) ;=> \"ababab\""
|
||||
([subject])
|
||||
([subject n]))
|
||||
|
||||
(defn decode-unsigned
|
||||
"Decodes an unsigned integer from binary.
|
||||
(binary/decode-unsigned <<0 0 0 42>>) ;=> 42"
|
||||
([subject])
|
||||
([subject endianness]))
|
||||
|
||||
(defn encode-unsigned
|
||||
"Encodes an unsigned integer to binary.
|
||||
(binary/encode-unsigned 42) ;=> <<42>>"
|
||||
([value])
|
||||
([value endianness]))
|
||||
|
||||
(defn longest-common-prefix
|
||||
"Returns the length of the longest common prefix of binaries.
|
||||
(binary/longest-common-prefix [\"abc\" \"abd\"]) ;=> 2"
|
||||
[binaries])
|
||||
|
||||
(defn longest-common-suffix
|
||||
"Returns the length of the longest common suffix."
|
||||
[binaries])
|
||||
|
||||
(defn compile-pattern
|
||||
"Pre-compiles a search pattern for repeated use.
|
||||
(let [pat (binary/compile-pattern \",\")] (binary/split data pat))"
|
||||
[pattern])
|
||||
|
||||
(defn referenced-byte-size
|
||||
"Returns the size of the referenced binary (before sub-binary optimization)."
|
||||
[binary])
|
||||
@@ -0,0 +1,123 @@
|
||||
(ns calendar
|
||||
"Erlang :calendar module — date and time calculations.
|
||||
|
||||
In CljElixir: (calendar/local-time), (calendar/universal-time), etc.
|
||||
Works with Erlang date/time tuples: {Year Month Day} and {Hour Min Sec}.")
|
||||
|
||||
(defn local-time
|
||||
"Returns the current local datetime as {{Y M D} {H M S}}.
|
||||
(calendar/local-time) ;=> {{2024 3 9} {12 30 0}}"
|
||||
[])
|
||||
|
||||
(defn universal-time
|
||||
"Returns the current UTC datetime as {{Y M D} {H M S}}.
|
||||
(calendar/universal-time) ;=> {{2024 3 9} {12 30 0}}"
|
||||
[])
|
||||
|
||||
(defn local-time-to-universal-time-dst
|
||||
"Converts local time to UTC, handling DST. Returns list of possible results."
|
||||
[datetime])
|
||||
|
||||
(defn universal-time-to-local-time
|
||||
"Converts UTC to local time."
|
||||
[datetime])
|
||||
|
||||
(defn now-to-datetime
|
||||
"Converts erlang:now/0 tuple to datetime tuple."
|
||||
[now])
|
||||
|
||||
(defn now-to-local-time
|
||||
"Converts erlang:now/0 to local datetime."
|
||||
[now])
|
||||
|
||||
(defn now-to-universal-time
|
||||
"Converts erlang:now/0 to UTC datetime."
|
||||
[now])
|
||||
|
||||
(defn datetime-to-gregorian-seconds
|
||||
"Converts {{Y M D} {H M S}} to Gregorian seconds (since year 0).
|
||||
(calendar/datetime-to-gregorian-seconds {{2024 1 1} {0 0 0}}) ;=> 63871..."
|
||||
[datetime])
|
||||
|
||||
(defn gregorian-seconds-to-datetime
|
||||
"Converts Gregorian seconds back to {{Y M D} {H M S}}.
|
||||
(calendar/gregorian-seconds-to-datetime 63871...)"
|
||||
[seconds])
|
||||
|
||||
(defn date-to-gregorian-days
|
||||
"Converts {Year Month Day} to Gregorian day count.
|
||||
(calendar/date-to-gregorian-days {2024 1 1})"
|
||||
([date])
|
||||
([year month day]))
|
||||
|
||||
(defn gregorian-days-to-date
|
||||
"Converts Gregorian day count to {Year Month Day}."
|
||||
[days])
|
||||
|
||||
(defn day-of-the-week
|
||||
"Returns day of week (1=Monday, 7=Sunday).
|
||||
(calendar/day-of-the-week {2024 3 9}) ;=> 6"
|
||||
([date])
|
||||
([year month day]))
|
||||
|
||||
(defn is-leap-year
|
||||
"Returns true if year is a leap year.
|
||||
(calendar/is-leap-year 2024) ;=> true"
|
||||
[year])
|
||||
|
||||
(defn last-day-of-the-month
|
||||
"Returns the last day of the month.
|
||||
(calendar/last-day-of-the-month 2024 2) ;=> 29"
|
||||
[year month])
|
||||
|
||||
(defn valid-date
|
||||
"Returns true if the date is valid.
|
||||
(calendar/valid-date {2024 2 29}) ;=> true"
|
||||
([date])
|
||||
([year month day]))
|
||||
|
||||
(defn iso-week-number
|
||||
"Returns {year week} for a date.
|
||||
(calendar/iso-week-number {2024 3 9}) ;=> {2024 10}"
|
||||
([date])
|
||||
([year month day]))
|
||||
|
||||
(defn time-difference
|
||||
"Returns the time difference between two datetimes.
|
||||
(calendar/time-difference dt1 dt2) ;=> {days {hours mins secs}}"
|
||||
[datetime1 datetime2])
|
||||
|
||||
(defn seconds-to-daystime
|
||||
"Converts seconds to {days {hours minutes seconds}}.
|
||||
(calendar/seconds-to-daystime 90061) ;=> {1 {1 1 1}}"
|
||||
[seconds])
|
||||
|
||||
(defn seconds-to-time
|
||||
"Converts seconds to {hours minutes seconds}.
|
||||
(calendar/seconds-to-time 3661) ;=> {1 1 1}"
|
||||
[seconds])
|
||||
|
||||
(defn time-to-seconds
|
||||
"Converts {hours minutes seconds} to seconds.
|
||||
(calendar/time-to-seconds {1 1 1}) ;=> 3661"
|
||||
[time])
|
||||
|
||||
(defn system-time-to-local-time
|
||||
"Converts system time to local datetime."
|
||||
[time unit])
|
||||
|
||||
(defn system-time-to-universal-time
|
||||
"Converts system time to UTC datetime."
|
||||
[time unit])
|
||||
|
||||
(defn rfc3339-to-system-time
|
||||
"Parses RFC 3339 timestamp to system time.
|
||||
(calendar/rfc3339-to-system-time \"2024-03-09T12:00:00Z\") ;=> integer"
|
||||
([string])
|
||||
([string opts]))
|
||||
|
||||
(defn system-time-to-rfc3339
|
||||
"Converts system time to RFC 3339 string.
|
||||
(calendar/system-time-to-rfc3339 time) ;=> \"2024-03-09T12:00:00Z\""
|
||||
([time])
|
||||
([time opts]))
|
||||
@@ -0,0 +1,367 @@
|
||||
(ns clje.core
|
||||
"CljElixir core — stubs for clj-kondo/clojure-lsp.
|
||||
|
||||
These are builtin functions and special forms handled directly by the
|
||||
CljElixir transformer. Standard Clojure forms (defn, let, fn, if, etc.)
|
||||
are mapped via :lint-as in .clj-kondo/config.edn."
|
||||
(:refer-clojure :exclude [case send pr pr-str prn print-str
|
||||
vec vector subvec vector?
|
||||
str println cons list
|
||||
inc dec
|
||||
map filter concat take drop
|
||||
sort sort-by group-by frequencies distinct
|
||||
mapcat partition
|
||||
keys vals select-keys merge into
|
||||
get assoc dissoc update get-in assoc-in update-in
|
||||
count first rest seq
|
||||
conj nth peek pop
|
||||
reduce reduce-kv
|
||||
contains? empty? nil?
|
||||
not= with-open throw]))
|
||||
|
||||
;; ===== Special Forms (hooks handle these) =====
|
||||
(defmacro receive
|
||||
"BEAM message receive with pattern matching.
|
||||
(receive
|
||||
[:hello sender] (send sender :hi)
|
||||
[:quit] (System/halt 0)
|
||||
:after 5000 (println \"timeout\"))"
|
||||
[& clauses])
|
||||
(defmacro defmodule
|
||||
"Defines an Elixir module.
|
||||
(defmodule MyModule
|
||||
(defn my-func [x] (* x 2)))"
|
||||
[name & body])
|
||||
(defmacro case
|
||||
"Pattern matching (not constant matching like Clojure).
|
||||
(case val
|
||||
[a b] (+ a b)
|
||||
{:key v} v
|
||||
_ :default)"
|
||||
[expr & clauses])
|
||||
(defmacro with
|
||||
"Monadic binding — chains pattern matches, short-circuits on mismatch.
|
||||
(with [{:ok val1} (step1)
|
||||
{:ok val2} (step2 val1)]
|
||||
(+ val1 val2))"
|
||||
[bindings & body])
|
||||
(defmacro ns
|
||||
"Declares the module namespace (primary module declaration).
|
||||
(ns MyApp.Router
|
||||
(require [Logger])
|
||||
(use [CljElixir.Core]))"
|
||||
[name & body])
|
||||
|
||||
;; ===== BEAM Concurrency =====
|
||||
(defn spawn
|
||||
"Spawns a new BEAM process. Returns PID.
|
||||
(spawn (fn [] (IO/puts \"hello from new process\")))"
|
||||
[f])
|
||||
(defn send
|
||||
"Sends a message to a process. Returns the message.
|
||||
(send pid {:hello \"world\"})"
|
||||
[pid msg])
|
||||
(defn monitor
|
||||
"Monitors a process. Returns a reference.
|
||||
(monitor pid)
|
||||
(monitor :process pid)"
|
||||
([pid]) ([type pid]))
|
||||
(defn link
|
||||
"Creates a bidirectional link between processes.
|
||||
(link pid)"
|
||||
[pid])
|
||||
(defn unlink
|
||||
"Removes a link between processes.
|
||||
(unlink pid)"
|
||||
[pid])
|
||||
(defn alive?
|
||||
"Returns true if the process is alive.
|
||||
(alive? pid) ;=> true"
|
||||
[pid])
|
||||
|
||||
;; ===== Arithmetic =====
|
||||
(defn inc
|
||||
"Increments by 1.
|
||||
(inc 5) ;=> 6"
|
||||
[x])
|
||||
(defn dec
|
||||
"Decrements by 1.
|
||||
(dec 5) ;=> 4"
|
||||
[x])
|
||||
|
||||
;; ===== String & Output =====
|
||||
(defn str
|
||||
"Concatenates arguments into a string.
|
||||
(str \"hello\" \" \" \"world\") ;=> \"hello world\""
|
||||
[& args])
|
||||
(defn println
|
||||
"Prints arguments followed by newline.
|
||||
(println \"hello\")"
|
||||
[& args])
|
||||
(defn pr-str
|
||||
"Returns a string representation (EDN-like).
|
||||
(pr-str {:a 1}) ;=> \"{:a 1}\""
|
||||
[& args])
|
||||
(defn prn
|
||||
"Prints EDN representation followed by newline."
|
||||
[& args])
|
||||
(defn pr
|
||||
"Prints EDN representation (no newline)."
|
||||
[& args])
|
||||
(defn print-str
|
||||
"Returns a human-readable string."
|
||||
[& args])
|
||||
|
||||
;; ===== BEAM Types & Interop =====
|
||||
(defn tuple
|
||||
"Creates a BEAM tuple from arguments.
|
||||
(tuple :ok \"value\") ;=> #el[:ok \"value\"]"
|
||||
[& args])
|
||||
(defn clojurify
|
||||
"Converts Elixir types to Clojure equivalents (keyword lists → maps, etc.).
|
||||
(clojurify elixir-val)"
|
||||
[val])
|
||||
(defn elixirify
|
||||
"Converts Clojure types to Elixir equivalents (maps → keyword lists, etc.).
|
||||
(elixirify clj-val)"
|
||||
[val])
|
||||
(defn hd
|
||||
"Returns the head (first element) of a list.
|
||||
(hd [1 2 3]) ;=> 1"
|
||||
[coll])
|
||||
(defn tl
|
||||
"Returns the tail (rest) of a list.
|
||||
(tl [1 2 3]) ;=> [2 3]"
|
||||
[coll])
|
||||
(defn cons
|
||||
"Prepends an element to a list.
|
||||
(cons 1 [2 3]) ;=> [1 2 3]"
|
||||
[head tail])
|
||||
(defn list
|
||||
"Creates a list from arguments.
|
||||
(list 1 2 3) ;=> [1 2 3]"
|
||||
[& args])
|
||||
|
||||
;; ===== BEAM Type Guards =====
|
||||
(defn is-binary
|
||||
"Returns true if `x` is a binary (string). Allowed in guards.
|
||||
(is-binary \"hello\") ;=> true"
|
||||
[x])
|
||||
(defn is-integer
|
||||
"Returns true if `x` is an integer. Allowed in guards."
|
||||
[x])
|
||||
(defn is-float
|
||||
"Returns true if `x` is a float. Allowed in guards."
|
||||
[x])
|
||||
(defn is-number
|
||||
"Returns true if `x` is a number. Allowed in guards."
|
||||
[x])
|
||||
(defn is-atom
|
||||
"Returns true if `x` is an atom. Allowed in guards."
|
||||
[x])
|
||||
(defn is-list
|
||||
"Returns true if `x` is a list. Allowed in guards."
|
||||
[x])
|
||||
(defn is-map
|
||||
"Returns true if `x` is a map. Allowed in guards."
|
||||
[x])
|
||||
(defn is-tuple
|
||||
"Returns true if `x` is a tuple. Allowed in guards."
|
||||
[x])
|
||||
(defn is-pid
|
||||
"Returns true if `x` is a PID. Allowed in guards."
|
||||
[x])
|
||||
(defn is-boolean
|
||||
"Returns true if `x` is a boolean. Allowed in guards."
|
||||
[x])
|
||||
(defn is-nil
|
||||
"Returns true if `x` is nil. Allowed in guards."
|
||||
[x])
|
||||
(defn is-function
|
||||
"Returns true if `x` is a function, optionally with given `arity`.
|
||||
(is-function f) ;=> true
|
||||
(is-function f 2) ;=> true if f takes 2 args"
|
||||
([x]) ([x arity]))
|
||||
|
||||
;; ===== Vectors =====
|
||||
(defn vec
|
||||
"Converts a collection to a PersistentVector.
|
||||
(vec [1 2 3])"
|
||||
[coll])
|
||||
(defn vector
|
||||
"Creates a PersistentVector from arguments.
|
||||
(vector 1 2 3)"
|
||||
[& args])
|
||||
(defn subvec
|
||||
"Returns a subvector from `start` to `end`.
|
||||
(subvec v 1 3)"
|
||||
([v start]) ([v start end]))
|
||||
(defn vector?
|
||||
"Returns true if `x` is a PersistentVector.
|
||||
(vector? (vector 1 2 3)) ;=> true"
|
||||
[x])
|
||||
|
||||
;; ===== Core Data Operations =====
|
||||
(defn get
|
||||
"Gets value at `key` from collection. Returns `default` if missing.
|
||||
(get {:a 1} :a) ;=> 1
|
||||
(get {:a 1} :b :not-found) ;=> :not-found"
|
||||
([coll key]) ([coll key default]))
|
||||
(defn assoc
|
||||
"Associates `key` with `val` in collection.
|
||||
(assoc {:a 1} :b 2) ;=> {:a 1 :b 2}"
|
||||
[coll key val])
|
||||
(defn dissoc
|
||||
"Dissociates `key` from collection.
|
||||
(dissoc {:a 1 :b 2} :a) ;=> {:b 2}"
|
||||
[coll key])
|
||||
(defn update
|
||||
"Updates value at `key` by applying `f`.
|
||||
(update {:a 1} :a inc) ;=> {:a 2}"
|
||||
([coll key f]) ([coll key f & args]))
|
||||
(defn get-in
|
||||
"Gets value at nested `path`.
|
||||
(get-in {:a {:b 1}} [:a :b]) ;=> 1"
|
||||
([coll path]) ([coll path default]))
|
||||
(defn assoc-in
|
||||
"Associates value at nested `path`.
|
||||
(assoc-in {} [:a :b] 1) ;=> {:a {:b 1}}"
|
||||
[coll path val])
|
||||
(defn update-in
|
||||
"Updates value at nested `path` by applying `f`.
|
||||
(update-in {:a {:b 1}} [:a :b] inc) ;=> {:a {:b 2}}"
|
||||
[coll path f])
|
||||
(defn contains?
|
||||
"Returns true if `coll` contains `key`.
|
||||
(contains? {:a 1} :a) ;=> true"
|
||||
[coll key])
|
||||
(defn empty?
|
||||
"Returns true if `coll` is empty.
|
||||
(empty? []) ;=> true"
|
||||
[coll])
|
||||
(defn nil?
|
||||
"Returns true if `x` is nil.
|
||||
(nil? nil) ;=> true"
|
||||
[x])
|
||||
(defn count
|
||||
"Returns the number of elements.
|
||||
(count [1 2 3]) ;=> 3"
|
||||
[coll])
|
||||
(defn first
|
||||
"Returns the first element.
|
||||
(first [1 2 3]) ;=> 1"
|
||||
[coll])
|
||||
(defn rest
|
||||
"Returns all but the first element.
|
||||
(rest [1 2 3]) ;=> [2 3]"
|
||||
[coll])
|
||||
(defn seq
|
||||
"Returns a seq on the collection, or nil if empty.
|
||||
(seq [1 2 3]) ;=> (1 2 3)"
|
||||
[coll])
|
||||
(defn conj
|
||||
"Adds element to collection (position depends on type).
|
||||
(conj [1 2] 3) ;=> [1 2 3]"
|
||||
[coll x])
|
||||
(defn nth
|
||||
"Returns element at `index`.
|
||||
(nth [10 20 30] 1) ;=> 20"
|
||||
([coll index]) ([coll index not-found]))
|
||||
(defn peek
|
||||
"Returns the most accessible element.
|
||||
(peek [1 2 3]) ;=> 3"
|
||||
[coll])
|
||||
(defn pop
|
||||
"Returns collection without the most accessible element.
|
||||
(pop [1 2 3]) ;=> [1 2]"
|
||||
[coll])
|
||||
|
||||
;; ===== Reducing =====
|
||||
(defn reduce
|
||||
"Reduces collection with `f`.
|
||||
(reduce + [1 2 3]) ;=> 6
|
||||
(reduce + 0 [1 2 3]) ;=> 6"
|
||||
([f coll]) ([f init coll]))
|
||||
(defn reduce-kv
|
||||
"Reduces a map with `f` receiving (acc, key, value).
|
||||
(reduce-kv (fn [acc k v] (+ acc v)) 0 {:a 1 :b 2}) ;=> 3"
|
||||
[f init coll])
|
||||
|
||||
;; ===== Sequence Operations =====
|
||||
(defn map
|
||||
"Applies `f` to each element, returns a list.
|
||||
(map inc [1 2 3]) ;=> [2 3 4]"
|
||||
[f coll])
|
||||
(defn filter
|
||||
"Returns elements where `f` returns truthy.
|
||||
(filter (fn [x] (> x 2)) [1 2 3 4]) ;=> [3 4]"
|
||||
[f coll])
|
||||
(defn concat
|
||||
"Concatenates collections.
|
||||
(concat [1 2] [3 4]) ;=> [1 2 3 4]"
|
||||
[& colls])
|
||||
(defn take
|
||||
"Returns first `n` elements.
|
||||
(take 2 [1 2 3 4]) ;=> [1 2]"
|
||||
[n coll])
|
||||
(defn drop
|
||||
"Drops first `n` elements.
|
||||
(drop 2 [1 2 3 4]) ;=> [3 4]"
|
||||
[n coll])
|
||||
(defn sort
|
||||
"Sorts a collection.
|
||||
(sort [3 1 2]) ;=> [1 2 3]
|
||||
(sort > [3 1 2]) ;=> [3 2 1]"
|
||||
([coll]) ([comp coll]))
|
||||
(defn sort-by
|
||||
"Sorts by the result of `keyfn`.
|
||||
(sort-by :name [{:name \"b\"} {:name \"a\"}])"
|
||||
([keyfn coll]) ([keyfn comp coll]))
|
||||
(defn group-by
|
||||
"Groups elements by the result of `f`.
|
||||
(group-by even? [1 2 3 4]) ;=> {false [1 3] true [2 4]}"
|
||||
[f coll])
|
||||
(defn frequencies
|
||||
"Returns a map of element → count.
|
||||
(frequencies [:a :b :a]) ;=> {:a 2 :b 1}"
|
||||
[coll])
|
||||
(defn distinct
|
||||
"Returns unique elements.
|
||||
(distinct [1 2 1 3]) ;=> [1 2 3]"
|
||||
[coll])
|
||||
(defn mapcat
|
||||
"Maps then concatenates results.
|
||||
(mapcat (fn [x] [x x]) [1 2 3]) ;=> [1 1 2 2 3 3]"
|
||||
[f coll])
|
||||
(defn partition
|
||||
"Partitions collection into groups of `n`.
|
||||
(partition 2 [1 2 3 4]) ;=> [[1 2] [3 4]]"
|
||||
([n coll]) ([n step coll]) ([n step pad coll]))
|
||||
(defn keys
|
||||
"Returns keys of a map.
|
||||
(keys {:a 1 :b 2}) ;=> [:a :b]"
|
||||
[m])
|
||||
(defn vals
|
||||
"Returns values of a map.
|
||||
(vals {:a 1 :b 2}) ;=> [1 2]"
|
||||
[m])
|
||||
(defn select-keys
|
||||
"Selects only specified `ks` from map.
|
||||
(select-keys {:a 1 :b 2 :c 3} [:a :c]) ;=> {:a 1 :c 3}"
|
||||
[m ks])
|
||||
(defn merge
|
||||
"Merges maps. Later maps take precedence.
|
||||
(merge {:a 1} {:b 2}) ;=> {:a 1 :b 2}"
|
||||
[& maps])
|
||||
(defn into
|
||||
"Pours elements from `from` into `to`.
|
||||
(into {} [[:a 1] [:b 2]]) ;=> {:a 1 :b 2}"
|
||||
[to from])
|
||||
(defn not=
|
||||
"Returns true if arguments are not equal.
|
||||
(not= 1 2) ;=> true"
|
||||
[& args])
|
||||
(defn throw
|
||||
"Throws a value (caught by try/catch :throw).
|
||||
(throw :some-error)"
|
||||
[value])
|
||||
@@ -0,0 +1,120 @@
|
||||
(ns crypto
|
||||
"Erlang :crypto module — cryptographic functions.
|
||||
|
||||
In CljElixir: (crypto/hash :sha256 data), (crypto/strong-rand-bytes 16), etc.
|
||||
Wraps OpenSSL for hashing, encryption, and random number generation.")
|
||||
|
||||
(defn hash
|
||||
"Computes a hash digest.
|
||||
(crypto/hash :sha256 \"hello\") ;=> <<binary hash>>
|
||||
Algorithms: :md5, :sha, :sha224, :sha256, :sha384, :sha512, :sha3-256, etc."
|
||||
[type data])
|
||||
|
||||
(defn mac
|
||||
"Computes a Message Authentication Code.
|
||||
(crypto/mac :hmac :sha256 key data)"
|
||||
([type sub-type key data])
|
||||
([type sub-type key data mac-length]))
|
||||
|
||||
(defn hash-init
|
||||
"Initializes incremental hashing.
|
||||
(crypto/hash-init :sha256)"
|
||||
[type])
|
||||
|
||||
(defn hash-update
|
||||
"Updates incremental hash with more data.
|
||||
(crypto/hash-update state data)"
|
||||
[state data])
|
||||
|
||||
(defn hash-final
|
||||
"Finalizes incremental hash. Returns the digest."
|
||||
[state])
|
||||
|
||||
(defn strong-rand-bytes
|
||||
"Generates `n` cryptographically strong random bytes.
|
||||
(crypto/strong-rand-bytes 16) ;=> <<16 random bytes>>"
|
||||
[n])
|
||||
|
||||
(defn crypto-one-time
|
||||
"One-shot symmetric encryption/decryption.
|
||||
(crypto/crypto-one-time :aes-256-ctr key iv data true) ;=> encrypted"
|
||||
([cipher key iv data encrypt-flag])
|
||||
([cipher key data encrypt-flag]))
|
||||
|
||||
(defn crypto-one-time-aead
|
||||
"One-shot AEAD encryption/decryption (e.g., AES-GCM).
|
||||
(crypto/crypto-one-time-aead :aes-256-gcm key iv data aad true)"
|
||||
([cipher key iv data aad encrypt-flag])
|
||||
([cipher key iv data aad tag-length encrypt-flag]))
|
||||
|
||||
(defn crypto-init
|
||||
"Initializes streaming encryption/decryption.
|
||||
3-arity: (crypto/crypto-init cipher key encrypt-flag)
|
||||
4-arity: (crypto/crypto-init cipher key iv encrypt-flag-or-opts)"
|
||||
([cipher key encrypt-flag])
|
||||
([cipher key iv encrypt-flag-or-opts]))
|
||||
|
||||
(defn crypto-update
|
||||
"Updates streaming encryption with more data."
|
||||
[state data])
|
||||
|
||||
(defn crypto-final
|
||||
"Finalizes streaming encryption."
|
||||
[state])
|
||||
|
||||
(defn sign
|
||||
"Creates a digital signature.
|
||||
(crypto/sign :rsa :sha256 data private-key)"
|
||||
([algorithm digest-type data key])
|
||||
([algorithm digest-type data key opts]))
|
||||
|
||||
(defn verify
|
||||
"Verifies a digital signature.
|
||||
(crypto/verify :rsa :sha256 data signature public-key)"
|
||||
([algorithm digest-type data signature key])
|
||||
([algorithm digest-type data signature key opts]))
|
||||
|
||||
(defn generate-key
|
||||
"Generates a key pair.
|
||||
(crypto/generate-key :ecdh :secp256r1)"
|
||||
([type params])
|
||||
([type params private-key]))
|
||||
|
||||
(defn compute-key
|
||||
"Computes shared secret from key exchange."
|
||||
([type others-public-key my-private-key params])
|
||||
([type others-public-key my-private-key shared-info params]))
|
||||
|
||||
(defn supports
|
||||
"Returns lists of supported algorithms.
|
||||
(crypto/supports) ;=> [{:ciphers [...]}, {:hashs [...]}, ...]"
|
||||
([])
|
||||
([category]))
|
||||
|
||||
(defn hash-info
|
||||
"Returns information about a hash algorithm."
|
||||
[type])
|
||||
|
||||
(defn cipher-info
|
||||
"Returns information about a cipher."
|
||||
[cipher])
|
||||
|
||||
(defn ec-curves
|
||||
"Returns supported elliptic curves."
|
||||
[])
|
||||
|
||||
(defn rand-seed
|
||||
"Seeds the random number generator.
|
||||
(crypto/rand-seed seed)"
|
||||
([seed])
|
||||
([alg-or-state seed]))
|
||||
|
||||
(defn rand-uniform
|
||||
"Returns a random integer in 1..n.
|
||||
(crypto/rand-uniform 100) ;=> 42"
|
||||
[n])
|
||||
|
||||
(defn exor
|
||||
"XORs two equal-length binaries.
|
||||
(crypto/exor bin1 bin2)"
|
||||
[bin1 bin2])
|
||||
@@ -0,0 +1,93 @@
|
||||
(ns io
|
||||
"Erlang :io module — I/O protocol operations.
|
||||
|
||||
In CljElixir: (io/format \"Hello ~s!~n\" [\"world\"]), etc.
|
||||
Lower-level than Elixir's IO module. Uses charlists and format strings.")
|
||||
|
||||
(defn format
|
||||
"Formatted output (like C printf).
|
||||
(io/format \"Hello ~s!~n\" [\"world\"]) ;=> prints 'Hello world!\\n'
|
||||
(io/format device \"~p~n\" [term]) ;=> pretty-print to device
|
||||
|
||||
Common format specs:
|
||||
~s string ~w write (Erlang term)
|
||||
~p pretty-print ~f float
|
||||
~e scientific ~b integer base 10
|
||||
~.Xb integer base X ~n newline
|
||||
~c character ~i ignore"
|
||||
([format args])
|
||||
([device format args]))
|
||||
|
||||
(defn fwrite
|
||||
"Like format but returns :ok or {:error reason}."
|
||||
([format args])
|
||||
([device format args]))
|
||||
|
||||
(defn get-line
|
||||
"Reads a line from standard input. Returns charlist or :eof.
|
||||
(io/get-line \"prompt> \")"
|
||||
([prompt])
|
||||
([device prompt]))
|
||||
|
||||
(defn put-chars
|
||||
"Writes characters to the IO device.
|
||||
(io/put-chars \"hello\")"
|
||||
([chars])
|
||||
([device chars]))
|
||||
|
||||
(defn nl
|
||||
"Writes a newline.
|
||||
(io/nl)"
|
||||
([])
|
||||
([device]))
|
||||
|
||||
(defn read
|
||||
"Reads an Erlang term from input. Returns {:ok term} or {:error reason}.
|
||||
(io/read \"enter term> \")"
|
||||
([prompt])
|
||||
([device prompt]))
|
||||
|
||||
(defn write
|
||||
"Writes an Erlang term.
|
||||
(io/write {:a 1}) ;=> prints '{a,1}'"
|
||||
([term])
|
||||
([device term]))
|
||||
|
||||
(defn scan-erl-form
|
||||
"Scans an Erlang form from input."
|
||||
([prompt])
|
||||
([device prompt])
|
||||
([device prompt start-line]))
|
||||
|
||||
(defn parse-erl-form
|
||||
"Parses an Erlang form from input."
|
||||
([prompt])
|
||||
([device prompt])
|
||||
([device prompt start-line]))
|
||||
|
||||
(defn setopts
|
||||
"Sets IO device options.
|
||||
(io/setopts [{:encoding :unicode}])"
|
||||
([opts])
|
||||
([device opts]))
|
||||
|
||||
(defn getopts
|
||||
"Gets IO device options."
|
||||
([])
|
||||
([device]))
|
||||
|
||||
(defn columns
|
||||
"Returns the terminal column count.
|
||||
(io/columns) ;=> {:ok 120}"
|
||||
([])
|
||||
([device]))
|
||||
|
||||
(defn rows
|
||||
"Returns the terminal row count.
|
||||
(io/rows) ;=> {:ok 40}"
|
||||
([])
|
||||
([device]))
|
||||
|
||||
(defn printable-range
|
||||
"Returns the printable character range (:unicode or :latin1)."
|
||||
[])
|
||||
@@ -0,0 +1,558 @@
|
||||
(ns erlang
|
||||
"Erlang :erlang module — BEAM runtime BIFs (Built-In Functions).
|
||||
|
||||
In CljElixir: (erlang/self), (erlang/band x y), etc.
|
||||
These are the lowest-level BEAM operations, many are used internally.")
|
||||
|
||||
;; --- Process ---
|
||||
|
||||
(defn self
|
||||
"Returns the PID of the calling process.
|
||||
(erlang/self) ;=> #PID<0.123.0>"
|
||||
[])
|
||||
|
||||
(defn spawn
|
||||
"Spawns a new process. Returns PID.
|
||||
(erlang/spawn (fn [] (do-work)))
|
||||
(erlang/spawn Module :fun [args])"
|
||||
([fun])
|
||||
([module function args]))
|
||||
|
||||
(defn spawn-link
|
||||
"Spawns and links a process."
|
||||
([fun])
|
||||
([module function args]))
|
||||
|
||||
(defn spawn-monitor
|
||||
"Spawns and monitors a process. Returns {pid ref}."
|
||||
([fun])
|
||||
([module function args]))
|
||||
|
||||
(defn send
|
||||
"Sends a message. Same as `!` operator.
|
||||
(erlang/send pid :hello)"
|
||||
([dest msg])
|
||||
([dest msg opts]))
|
||||
|
||||
(defn exit
|
||||
"Sends an exit signal.
|
||||
(erlang/exit :normal)
|
||||
(erlang/exit pid :kill)"
|
||||
([reason])
|
||||
([pid reason]))
|
||||
|
||||
(defn link
|
||||
"Creates a bidirectional link.
|
||||
(erlang/link pid)"
|
||||
[pid])
|
||||
|
||||
(defn unlink
|
||||
"Removes a link.
|
||||
(erlang/unlink pid)"
|
||||
[pid])
|
||||
|
||||
(defn monitor
|
||||
"Starts monitoring a process.
|
||||
(erlang/monitor :process pid)"
|
||||
[type item])
|
||||
|
||||
(defn demonitor
|
||||
"Stops monitoring.
|
||||
(erlang/demonitor ref)"
|
||||
([ref])
|
||||
([ref opts]))
|
||||
|
||||
(defn process-flag
|
||||
"Sets process flags.
|
||||
(erlang/process-flag :trap-exit true)"
|
||||
([flag value])
|
||||
([pid flag value]))
|
||||
|
||||
(defn process-info
|
||||
"Returns info about a process.
|
||||
(erlang/process-info pid)
|
||||
(erlang/process-info pid :message-queue-len)"
|
||||
([pid])
|
||||
([pid item]))
|
||||
|
||||
(defn processes
|
||||
"Returns a list of all process PIDs."
|
||||
[])
|
||||
|
||||
(defn is-process-alive
|
||||
"Returns true if `pid` is alive."
|
||||
[pid])
|
||||
|
||||
(defn register
|
||||
"Registers a process under a name.
|
||||
(erlang/register :my-server (erlang/self))"
|
||||
[name pid])
|
||||
|
||||
(defn unregister
|
||||
"Unregisters a name."
|
||||
[name])
|
||||
|
||||
(defn whereis
|
||||
"Returns PID for registered name, or :undefined."
|
||||
[name])
|
||||
|
||||
(defn registered
|
||||
"Returns list of all registered names."
|
||||
[])
|
||||
|
||||
(defn group-leader
|
||||
"Gets or sets the group leader.
|
||||
(erlang/group-leader)
|
||||
(erlang/group-leader new-leader pid)"
|
||||
([])
|
||||
([leader pid]))
|
||||
|
||||
(defn hibernate
|
||||
"Puts process in hibernate mode (frees heap)."
|
||||
[module function args])
|
||||
|
||||
;; --- Bitwise Operations ---
|
||||
|
||||
(defn band
|
||||
"Bitwise AND.
|
||||
(erlang/band 0xFF 0x0F) ;=> 15"
|
||||
[int1 int2])
|
||||
|
||||
(defn bor
|
||||
"Bitwise OR.
|
||||
(erlang/bor 0x0F 0xF0) ;=> 255"
|
||||
[int1 int2])
|
||||
|
||||
(defn bxor
|
||||
"Bitwise XOR.
|
||||
(erlang/bxor 0xFF 0x0F) ;=> 240"
|
||||
[int1 int2])
|
||||
|
||||
(defn bnot
|
||||
"Bitwise NOT.
|
||||
(erlang/bnot 0) ;=> -1"
|
||||
[int])
|
||||
|
||||
(defn bsl
|
||||
"Bitwise shift left.
|
||||
(erlang/bsl 1 5) ;=> 32"
|
||||
[int shift])
|
||||
|
||||
(defn bsr
|
||||
"Bitwise shift right.
|
||||
(erlang/bsr 32 5) ;=> 1"
|
||||
[int shift])
|
||||
|
||||
;; --- Arithmetic ---
|
||||
|
||||
(defn abs
|
||||
"Returns absolute value.
|
||||
(erlang/abs -5) ;=> 5"
|
||||
[number])
|
||||
|
||||
(defn div
|
||||
"Integer division (truncated towards zero).
|
||||
(erlang/div 10 3) ;=> 3"
|
||||
[a b])
|
||||
|
||||
(defn rem
|
||||
"Integer remainder.
|
||||
(erlang/rem 10 3) ;=> 1"
|
||||
[a b])
|
||||
|
||||
(defn float
|
||||
"Converts to float.
|
||||
(erlang/float 42) ;=> 42.0"
|
||||
[number])
|
||||
|
||||
(defn round
|
||||
"Rounds to nearest integer.
|
||||
(erlang/round 3.5) ;=> 4"
|
||||
[number])
|
||||
|
||||
(defn trunc
|
||||
"Truncates to integer.
|
||||
(erlang/trunc 3.9) ;=> 3"
|
||||
[number])
|
||||
|
||||
(defn ceil
|
||||
"Ceiling (smallest integer >= number).
|
||||
(erlang/ceil 3.1) ;=> 4"
|
||||
[number])
|
||||
|
||||
(defn floor
|
||||
"Floor (largest integer <= number).
|
||||
(erlang/floor 3.9) ;=> 3"
|
||||
[number])
|
||||
|
||||
;; --- Tuple Operations ---
|
||||
|
||||
(defn element
|
||||
"Gets element at 1-based `index` from tuple.
|
||||
(erlang/element 1 #el[:a :b :c]) ;=> :a"
|
||||
[index tuple])
|
||||
|
||||
(defn setelement
|
||||
"Sets element at 1-based `index` in tuple.
|
||||
(erlang/setelement 1 #el[:a :b] :x) ;=> #el[:x :b]"
|
||||
[index tuple value])
|
||||
|
||||
(defn tuple-size
|
||||
"Returns the size of a tuple.
|
||||
(erlang/tuple-size #el[1 2 3]) ;=> 3"
|
||||
[tuple])
|
||||
|
||||
(defn make-tuple
|
||||
"Creates a tuple of `arity` filled with `init-value`.
|
||||
(erlang/make-tuple 3 0) ;=> #el[0 0 0]"
|
||||
([arity init-value])
|
||||
([arity default-value init-list]))
|
||||
|
||||
(defn append-element
|
||||
"Appends `element` to `tuple`.
|
||||
(erlang/append-element #el[1 2] 3) ;=> #el[1 2 3]"
|
||||
[tuple element])
|
||||
|
||||
(defn tuple-to-list
|
||||
"Converts a tuple to a list.
|
||||
(erlang/tuple-to-list #el[1 2 3]) ;=> [1 2 3]"
|
||||
[tuple])
|
||||
|
||||
(defn list-to-tuple
|
||||
"Converts a list to a tuple.
|
||||
(erlang/list-to-tuple [1 2 3]) ;=> #el[1 2 3]"
|
||||
[list])
|
||||
|
||||
;; --- Type Conversion ---
|
||||
|
||||
(defn atom-to-list
|
||||
"Converts atom to charlist.
|
||||
(erlang/atom-to-list :hello) ;=> 'hello'"
|
||||
[atom])
|
||||
|
||||
(defn list-to-atom
|
||||
"Converts charlist to atom."
|
||||
[charlist])
|
||||
|
||||
(defn atom-to-binary
|
||||
"Converts atom to binary string.
|
||||
(erlang/atom-to-binary :hello) ;=> \"hello\""
|
||||
([atom])
|
||||
([atom encoding]))
|
||||
|
||||
(defn binary-to-atom
|
||||
"Converts binary to atom."
|
||||
([binary])
|
||||
([binary encoding]))
|
||||
|
||||
(defn binary-to-existing-atom
|
||||
"Converts binary to existing atom."
|
||||
([binary])
|
||||
([binary encoding]))
|
||||
|
||||
(defn integer-to-list
|
||||
"Converts integer to charlist.
|
||||
(erlang/integer-to-list 123) ;=> '123'
|
||||
(erlang/integer-to-list 255 16) ;=> 'FF'"
|
||||
([integer])
|
||||
([integer base]))
|
||||
|
||||
(defn list-to-integer
|
||||
"Converts charlist to integer."
|
||||
([charlist])
|
||||
([charlist base]))
|
||||
|
||||
(defn integer-to-binary
|
||||
"Converts integer to binary string.
|
||||
(erlang/integer-to-binary 123) ;=> \"123\""
|
||||
([integer])
|
||||
([integer base]))
|
||||
|
||||
(defn binary-to-integer
|
||||
"Converts binary to integer."
|
||||
([binary])
|
||||
([binary base]))
|
||||
|
||||
(defn float-to-list
|
||||
"Converts float to charlist."
|
||||
([float])
|
||||
([float opts]))
|
||||
|
||||
(defn float-to-binary
|
||||
"Converts float to binary."
|
||||
([float])
|
||||
([float opts]))
|
||||
|
||||
(defn list-to-float
|
||||
"Converts charlist to float."
|
||||
[charlist])
|
||||
|
||||
(defn binary-to-float
|
||||
"Converts binary to float."
|
||||
[binary])
|
||||
|
||||
(defn binary-to-list
|
||||
"Converts binary to list of bytes.
|
||||
(erlang/binary-to-list \"hello\") ;=> [104 101 108 108 111]"
|
||||
([binary])
|
||||
([binary start stop]))
|
||||
|
||||
(defn list-to-binary
|
||||
"Converts iolist to binary.
|
||||
(erlang/list-to-binary [104 101 108 108 111]) ;=> \"hello\""
|
||||
[iolist])
|
||||
|
||||
(defn iolist-to-binary
|
||||
"Converts iolist to binary.
|
||||
(erlang/iolist-to-binary [\"hello\" \" \" \"world\"]) ;=> \"hello world\""
|
||||
[iolist])
|
||||
|
||||
(defn iolist-size
|
||||
"Returns byte size of an iolist."
|
||||
[iolist])
|
||||
|
||||
(defn term-to-binary
|
||||
"Serializes a term to binary (External Term Format).
|
||||
(erlang/term-to-binary {:key \"value\"}) ;=> <<131, ...>>"
|
||||
([term])
|
||||
([term opts]))
|
||||
|
||||
(defn binary-to-term
|
||||
"Deserializes binary (ETF) to a term.
|
||||
(erlang/binary-to-term bin) ;=> {:key \"value\"}"
|
||||
([binary])
|
||||
([binary opts]))
|
||||
|
||||
;; --- Type Checks ---
|
||||
|
||||
(defn is-atom
|
||||
"Returns true if term is an atom."
|
||||
[term])
|
||||
|
||||
(defn is-binary
|
||||
"Returns true if term is a binary."
|
||||
[term])
|
||||
|
||||
(defn is-boolean
|
||||
"Returns true if term is a boolean."
|
||||
[term])
|
||||
|
||||
(defn is-float
|
||||
"Returns true if term is a float."
|
||||
[term])
|
||||
|
||||
(defn is-function
|
||||
"Returns true if term is a function."
|
||||
([term])
|
||||
([term arity]))
|
||||
|
||||
(defn is-integer
|
||||
"Returns true if term is an integer."
|
||||
[term])
|
||||
|
||||
(defn is-list
|
||||
"Returns true if term is a list."
|
||||
[term])
|
||||
|
||||
(defn is-map
|
||||
"Returns true if term is a map."
|
||||
[term])
|
||||
|
||||
(defn is-map-key
|
||||
"Returns true if key exists in map. Guard-safe."
|
||||
[map key])
|
||||
|
||||
(defn is-number
|
||||
"Returns true if term is a number."
|
||||
[term])
|
||||
|
||||
(defn is-pid
|
||||
"Returns true if term is a PID."
|
||||
[term])
|
||||
|
||||
(defn is-port
|
||||
"Returns true if term is a port."
|
||||
[term])
|
||||
|
||||
(defn is-reference
|
||||
"Returns true if term is a reference."
|
||||
[term])
|
||||
|
||||
(defn is-tuple
|
||||
"Returns true if term is a tuple."
|
||||
[term])
|
||||
|
||||
;; --- List Operations ---
|
||||
|
||||
(defn hd
|
||||
"Returns the head of a list.
|
||||
(erlang/hd [1 2 3]) ;=> 1"
|
||||
[list])
|
||||
|
||||
(defn tl
|
||||
"Returns the tail of a list.
|
||||
(erlang/tl [1 2 3]) ;=> [2 3]"
|
||||
[list])
|
||||
|
||||
(defn length
|
||||
"Returns the length of a list.
|
||||
(erlang/length [1 2 3]) ;=> 3"
|
||||
[list])
|
||||
|
||||
;; --- Binary/Bitstring ---
|
||||
|
||||
(defn byte-size
|
||||
"Returns byte size of a binary.
|
||||
(erlang/byte-size \"hello\") ;=> 5"
|
||||
[binary])
|
||||
|
||||
(defn bit-size
|
||||
"Returns bit size of a bitstring."
|
||||
[bitstring])
|
||||
|
||||
(defn binary-part
|
||||
"Extracts a part of a binary. Guard-safe.
|
||||
(erlang/binary-part \"hello\" 1 3) ;=> \"ell\""
|
||||
[binary start length])
|
||||
|
||||
(defn split-binary
|
||||
"Splits binary at position.
|
||||
(erlang/split-binary \"hello\" 3) ;=> {\"hel\" \"lo\"}"
|
||||
[binary pos])
|
||||
|
||||
;; --- Hash ---
|
||||
|
||||
(defn phash2
|
||||
"Portable hash function. Returns integer in 0..(range-1).
|
||||
(erlang/phash2 :my-term) ;=> 12345678
|
||||
(erlang/phash2 :my-term 100) ;=> 78"
|
||||
([term])
|
||||
([term range]))
|
||||
|
||||
;; --- Time ---
|
||||
|
||||
(defn monotonic-time
|
||||
"Returns monotonic time.
|
||||
(erlang/monotonic-time) ;=> native time units
|
||||
(erlang/monotonic-time :millisecond)"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn system-time
|
||||
"Returns system (wall-clock) time."
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn unique-integer
|
||||
"Returns a unique integer.
|
||||
(erlang/unique-integer [:positive :monotonic])"
|
||||
([])
|
||||
([modifiers]))
|
||||
|
||||
(defn timestamp
|
||||
"Returns {megasecs secs microsecs} tuple."
|
||||
[])
|
||||
|
||||
(defn convert-time-unit
|
||||
"Converts time between units.
|
||||
(erlang/convert-time-unit time :native :millisecond)"
|
||||
[time from-unit to-unit])
|
||||
|
||||
;; --- Node ---
|
||||
|
||||
(defn node
|
||||
"Returns the current node name.
|
||||
(erlang/node) ;=> :nonode@nohost"
|
||||
([])
|
||||
([arg]))
|
||||
|
||||
(defn nodes
|
||||
"Returns list of connected nodes.
|
||||
(erlang/nodes) ;=> [:node1@host ...]"
|
||||
([])
|
||||
([type]))
|
||||
|
||||
(defn is-alive
|
||||
"Returns true if the node is part of a distributed system."
|
||||
[])
|
||||
|
||||
;; --- System ---
|
||||
|
||||
(defn apply
|
||||
"Applies function. Like Kernel.apply.
|
||||
(erlang/apply Enum :map [[1 2 3] inc])
|
||||
(erlang/apply fun args)"
|
||||
([fun args])
|
||||
([module function args]))
|
||||
|
||||
(defn error
|
||||
"Raises an error.
|
||||
(erlang/error :badarg)
|
||||
(erlang/error {:my-error \"reason\"})"
|
||||
([reason])
|
||||
([reason args]))
|
||||
|
||||
(defn throw
|
||||
"Throws a term (caught by try/catch :throw)."
|
||||
[term])
|
||||
|
||||
(defn halt
|
||||
"Halts the runtime.
|
||||
(erlang/halt 0)"
|
||||
([])
|
||||
([status])
|
||||
([status opts]))
|
||||
|
||||
(defn garbage-collect
|
||||
"Forces garbage collection on current (or specified) process.
|
||||
(erlang/garbage-collect)"
|
||||
([])
|
||||
([pid]))
|
||||
|
||||
(defn memory
|
||||
"Returns memory usage info.
|
||||
(erlang/memory) ;=> [{:total N} {:processes N} ...]
|
||||
(erlang/memory :total) ;=> bytes"
|
||||
([])
|
||||
([type]))
|
||||
|
||||
(defn system-info
|
||||
"Returns system information.
|
||||
(erlang/system-info :process-count)
|
||||
(erlang/system-info :otp-release)"
|
||||
[item])
|
||||
|
||||
(defn statistics
|
||||
"Returns runtime statistics.
|
||||
(erlang/statistics :wall-clock)"
|
||||
[type])
|
||||
|
||||
(defn make-ref
|
||||
"Creates a unique reference."
|
||||
[])
|
||||
|
||||
(defn md5
|
||||
"Computes MD5 hash of binary. Returns 16-byte binary."
|
||||
[data])
|
||||
|
||||
(defn crc32
|
||||
"Computes CRC32 checksum."
|
||||
([data])
|
||||
([old-crc data]))
|
||||
|
||||
(defn adler32
|
||||
"Computes Adler-32 checksum."
|
||||
([data])
|
||||
([old-adler data]))
|
||||
|
||||
;; --- Map Operations ---
|
||||
|
||||
(defn map-get
|
||||
"Gets a value from a map. Guard-safe.
|
||||
(erlang/map-get :key my-map)"
|
||||
[key map])
|
||||
|
||||
(defn map-size
|
||||
"Returns the number of entries in a map. Guard-safe.
|
||||
(erlang/map-size {:a 1 :b 2}) ;=> 2"
|
||||
[map])
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
(ns ets
|
||||
"Erlang :ets module — Erlang Term Storage (in-memory tables).
|
||||
|
||||
In CljElixir: (ets/new :my-table [:set :public]), etc.
|
||||
ETS tables are mutable, concurrent, in-memory key-value stores.")
|
||||
|
||||
(defn new
|
||||
"Creates a new ETS table. Returns table ID.
|
||||
(ets/new :my-table [:set :public :named-table])
|
||||
Types: :set, :ordered-set, :bag, :duplicate-bag
|
||||
Access: :public, :protected (default), :private"
|
||||
[name opts])
|
||||
|
||||
(defn insert
|
||||
"Inserts one or more tuples. Returns true.
|
||||
(ets/insert table {:key \"value\"})
|
||||
(ets/insert table [{:a 1} {:b 2}])"
|
||||
[table objects])
|
||||
|
||||
(defn insert-new
|
||||
"Inserts only if key doesn't exist. Returns true/false."
|
||||
[table objects])
|
||||
|
||||
(defn lookup
|
||||
"Returns all objects matching `key`. Returns list.
|
||||
(ets/lookup table :key) ;=> [{:key \"value\"}]"
|
||||
[table key])
|
||||
|
||||
(defn lookup-element
|
||||
"Returns specific element at `pos` for `key`.
|
||||
(ets/lookup-element table :key 2) ;=> \"value\""
|
||||
([table key pos])
|
||||
([table key pos default]))
|
||||
|
||||
(defn member
|
||||
"Returns true if `key` exists.
|
||||
(ets/member table :key) ;=> true"
|
||||
[table key])
|
||||
|
||||
(defn delete
|
||||
"Deletes a table or entries matching `key`.
|
||||
(ets/delete table) ;=> deletes entire table
|
||||
(ets/delete table :key) ;=> deletes entry"
|
||||
([table])
|
||||
([table key]))
|
||||
|
||||
(defn delete-object
|
||||
"Deletes a specific object from the table."
|
||||
[table object])
|
||||
|
||||
(defn delete-all-objects
|
||||
"Deletes all objects from the table."
|
||||
[table])
|
||||
|
||||
(defn update-counter
|
||||
"Atomically updates a counter. Returns new value.
|
||||
(ets/update-counter table :hits 1) ;=> increments by 1
|
||||
(ets/update-counter table :hits {2 1}) ;=> increments pos 2 by 1"
|
||||
([table key update-op])
|
||||
([table key update-op default]))
|
||||
|
||||
(defn update-element
|
||||
"Atomically updates specific elements of a tuple."
|
||||
([table key element-spec])
|
||||
([table key element-spec default]))
|
||||
|
||||
(defn select
|
||||
"Selects objects matching a match specification.
|
||||
(ets/select table match-spec)"
|
||||
([table match-spec])
|
||||
([table match-spec limit]))
|
||||
|
||||
(defn match
|
||||
"Pattern matches objects in the table.
|
||||
(ets/match table {:_ :$1}) ;=> extracts matched values"
|
||||
([table pattern])
|
||||
([table pattern limit]))
|
||||
|
||||
(defn match-object
|
||||
"Returns full objects matching the pattern.
|
||||
(ets/match-object table {:_ :_}) ;=> all objects"
|
||||
([table pattern])
|
||||
([table pattern limit]))
|
||||
|
||||
(defn match-delete
|
||||
"Deletes all objects matching the pattern.
|
||||
(ets/match-delete table {:_ :_}) ;=> deletes all"
|
||||
[table pattern])
|
||||
|
||||
(defn select-delete
|
||||
"Deletes objects matching a match specification. Returns count."
|
||||
[table match-spec])
|
||||
|
||||
(defn select-count
|
||||
"Counts objects matching a match specification."
|
||||
[table match-spec])
|
||||
|
||||
(defn select-replace
|
||||
"Replaces objects matching a match specification."
|
||||
[table match-spec])
|
||||
|
||||
(defn first
|
||||
"Returns the first key in the table.
|
||||
(ets/first table) ;=> :some-key or :'$end_of_table'"
|
||||
[table])
|
||||
|
||||
(defn last
|
||||
"Returns the last key (only for ordered-set)."
|
||||
[table])
|
||||
|
||||
(defn next
|
||||
"Returns the key after `key`.
|
||||
(ets/next table :key) ;=> :next-key"
|
||||
[table key])
|
||||
|
||||
(defn prev
|
||||
"Returns the key before `key` (only for ordered-set)."
|
||||
[table key])
|
||||
|
||||
(defn tab2list
|
||||
"Converts entire table to a list.
|
||||
(ets/tab2list table) ;=> [{:key1 \"val1\"} ...]"
|
||||
[table])
|
||||
|
||||
(defn info
|
||||
"Returns information about the table.
|
||||
(ets/info table) ;=> keyword list
|
||||
(ets/info table :size) ;=> number of objects"
|
||||
([table])
|
||||
([table item]))
|
||||
|
||||
(defn rename
|
||||
"Renames a named table."
|
||||
[table name])
|
||||
|
||||
(defn give-away
|
||||
"Transfers table ownership to another process."
|
||||
[table pid gift-data])
|
||||
|
||||
(defn i
|
||||
"Prints brief info about all ETS tables."
|
||||
([])
|
||||
([table]))
|
||||
|
||||
(defn foldl
|
||||
"Folds left over table entries."
|
||||
[fun acc table])
|
||||
|
||||
(defn foldr
|
||||
"Folds right over table entries."
|
||||
[fun acc table])
|
||||
|
||||
(defn tab2file
|
||||
"Dumps table to a file."
|
||||
([table filename])
|
||||
([table filename opts]))
|
||||
|
||||
(defn file2tab
|
||||
"Loads table from a file."
|
||||
([filename])
|
||||
([filename opts]))
|
||||
|
||||
(defn whereis
|
||||
"Returns the table ID for a named table."
|
||||
[name])
|
||||
|
||||
(defn safe-fixtable
|
||||
"Fixes/unfixes a table for safe traversal."
|
||||
[table fix])
|
||||
|
||||
(defn fun2ms
|
||||
"Converts a literal fun to a match specification."
|
||||
[fun])
|
||||
|
||||
(defn test-ms
|
||||
"Tests a match specification against a tuple."
|
||||
[tuple match-spec])
|
||||
|
||||
(defn all
|
||||
"Returns a list of all ETS table IDs."
|
||||
[])
|
||||
@@ -0,0 +1,73 @@
|
||||
(ns filename
|
||||
"Erlang :filename module — file name manipulation.
|
||||
|
||||
In CljElixir: (filename/join [\"a\" \"b\"]), (filename/basename path), etc.
|
||||
Works with charlists (unlike Elixir's Path which uses binaries).")
|
||||
|
||||
(defn join
|
||||
"Joins path components.
|
||||
(filename/join [\"a\" \"b\" \"c\"]) ;=> 'a/b/c'
|
||||
(filename/join \"a\" \"b\") ;=> 'a/b'"
|
||||
([components])
|
||||
([name1 name2]))
|
||||
|
||||
(defn split
|
||||
"Splits a path into components.
|
||||
(filename/split \"/a/b/c\") ;=> [\"/\" \"a\" \"b\" \"c\"]"
|
||||
[name])
|
||||
|
||||
(defn basename
|
||||
"Returns the last component of the path.
|
||||
(filename/basename \"/a/b/c.txt\") ;=> 'c.txt'
|
||||
(filename/basename \"/a/b/c.txt\" \".txt\") ;=> 'c'"
|
||||
([name])
|
||||
([name ext]))
|
||||
|
||||
(defn dirname
|
||||
"Returns the directory component.
|
||||
(filename/dirname \"/a/b/c\") ;=> '/a/b'"
|
||||
[name])
|
||||
|
||||
(defn extension
|
||||
"Returns the file extension including the dot.
|
||||
(filename/extension \"file.ex\") ;=> '.ex'"
|
||||
[name])
|
||||
|
||||
(defn rootname
|
||||
"Returns path without extension.
|
||||
(filename/rootname \"file.ex\") ;=> 'file'"
|
||||
([name])
|
||||
([name ext]))
|
||||
|
||||
(defn absname
|
||||
"Converts to absolute path.
|
||||
(filename/absname \"file.txt\")"
|
||||
([name])
|
||||
([name dir]))
|
||||
|
||||
(defn flatten
|
||||
"Flattens a filename (resolves deep lists)."
|
||||
[name])
|
||||
|
||||
(defn nativename
|
||||
"Converts to native OS path format."
|
||||
[name])
|
||||
|
||||
(defn pathtype
|
||||
"Returns path type: :absolute, :relative, or :volumerelative.
|
||||
(filename/pathtype \"/a/b\") ;=> :absolute"
|
||||
[name])
|
||||
|
||||
(defn safe-relative-path
|
||||
"Returns a safe relative path or :unsafe."
|
||||
[name])
|
||||
|
||||
(defn find-src
|
||||
"Finds source file from object file name."
|
||||
([beam])
|
||||
([beam rules]))
|
||||
|
||||
(defn find-file
|
||||
"Finds a file in the given paths."
|
||||
([name paths])
|
||||
([name paths extensions]))
|
||||
@@ -0,0 +1,79 @@
|
||||
(ns gen_server
|
||||
"Erlang :gen_server module — generic server (low-level OTP API).
|
||||
|
||||
In CljElixir: prefer Elixir's GenServer module for most use cases.
|
||||
Use this for direct Erlang OTP interop.")
|
||||
|
||||
(defn start
|
||||
"Starts a gen_server. Returns {:ok pid} or {:error reason}.
|
||||
(gen_server/start module init-arg opts)"
|
||||
([module init-arg opts])
|
||||
([server-name module init-arg opts]))
|
||||
|
||||
(defn start-link
|
||||
"Starts a linked gen_server."
|
||||
([module init-arg opts])
|
||||
([server-name module init-arg opts]))
|
||||
|
||||
(defn call
|
||||
"Makes a synchronous call.
|
||||
(gen_server/call pid request) ;=> reply
|
||||
(gen_server/call pid request 5000)"
|
||||
([server-ref request])
|
||||
([server-ref request timeout]))
|
||||
|
||||
(defn cast
|
||||
"Sends an async message.
|
||||
(gen_server/cast pid message) ;=> :ok"
|
||||
[server-ref request])
|
||||
|
||||
(defn reply
|
||||
"Replies to a caller from within handle_call.
|
||||
(gen_server/reply from reply-value)"
|
||||
[client reply])
|
||||
|
||||
(defn stop
|
||||
"Stops the server.
|
||||
(gen_server/stop pid)"
|
||||
([server-ref])
|
||||
([server-ref reason])
|
||||
([server-ref reason timeout]))
|
||||
|
||||
(defn multi-call
|
||||
"Calls all registered servers on all/specified nodes."
|
||||
([name request])
|
||||
([nodes name request])
|
||||
([nodes name request timeout]))
|
||||
|
||||
(defn abcast
|
||||
"Casts to all registered servers on all/specified nodes."
|
||||
([name request])
|
||||
([nodes name request]))
|
||||
|
||||
(defn enter-loop
|
||||
"Makes calling process enter the gen_server receive loop."
|
||||
([module opts state])
|
||||
([module opts state server-name])
|
||||
([module opts state server-name timeout]))
|
||||
|
||||
(defn wait-response
|
||||
"Waits for a gen_server response."
|
||||
([request-id timeout])
|
||||
([request-id]))
|
||||
|
||||
(defn send-request
|
||||
"Sends an async request. Returns request-id.
|
||||
(gen_server/send-request pid request)"
|
||||
([server-ref request])
|
||||
([server-ref request label req-id-collection]))
|
||||
|
||||
(defn receive-response
|
||||
"Receives a response from send-request.
|
||||
(gen_server/receive-response request-id timeout)"
|
||||
([request-id timeout])
|
||||
([request-id]))
|
||||
|
||||
(defn check-response
|
||||
"Checks if a response has arrived (non-blocking).
|
||||
(gen_server/check-response msg request-id-or-collection)"
|
||||
[msg request-id-or-collection])
|
||||
@@ -0,0 +1,52 @@
|
||||
(ns gen_tcp
|
||||
"Erlang :gen_tcp module — TCP socket interface.
|
||||
|
||||
In CljElixir: (gen_tcp/listen port opts), (gen_tcp/accept socket), etc.
|
||||
Core networking module for TCP servers and clients on the BEAM.")
|
||||
|
||||
(defn listen
|
||||
"Starts listening on `port`. Returns {:ok listen-socket} or {:error reason}.
|
||||
(gen_tcp/listen 4000 [:binary {:active false} {:reuseaddr true}])"
|
||||
[port opts])
|
||||
|
||||
(defn accept
|
||||
"Accepts an incoming connection. Blocks until a connection arrives.
|
||||
(gen_tcp/accept listen-socket) ;=> {:ok socket}
|
||||
(gen_tcp/accept listen-socket 5000) ;=> with 5s timeout"
|
||||
([listen-socket])
|
||||
([listen-socket timeout]))
|
||||
|
||||
(defn connect
|
||||
"Connects to a TCP server. Returns {:ok socket} or {:error reason}.
|
||||
(gen_tcp/connect \"localhost\" 4000 [:binary {:active false}])
|
||||
(gen_tcp/connect {127 0 0 1} 4000 opts 5000) ;=> with timeout"
|
||||
([address port opts])
|
||||
([address port opts timeout]))
|
||||
|
||||
(defn send
|
||||
"Sends data over a TCP socket. Returns :ok or {:error reason}.
|
||||
(gen_tcp/send socket \"hello\")"
|
||||
[socket packet])
|
||||
|
||||
(defn recv
|
||||
"Receives data from a socket. Blocks until data arrives.
|
||||
(gen_tcp/recv socket 0) ;=> {:ok data} (0 = any amount)
|
||||
(gen_tcp/recv socket 1024 5000) ;=> with timeout"
|
||||
([socket length])
|
||||
([socket length timeout]))
|
||||
|
||||
(defn close
|
||||
"Closes a TCP socket.
|
||||
(gen_tcp/close socket) ;=> :ok"
|
||||
[socket])
|
||||
|
||||
(defn controlling-process
|
||||
"Transfers socket ownership to another process.
|
||||
(gen_tcp/controlling-process socket new-owner-pid) ;=> :ok"
|
||||
[socket pid])
|
||||
|
||||
(defn shutdown
|
||||
"Shuts down one or both directions of a socket.
|
||||
(gen_tcp/shutdown socket :write) ;=> :ok
|
||||
(gen_tcp/shutdown socket :read-write)"
|
||||
[socket how])
|
||||
@@ -0,0 +1,33 @@
|
||||
(ns gen_udp
|
||||
"Erlang :gen_udp module — UDP socket interface.
|
||||
|
||||
In CljElixir: (gen_udp/open port opts), (gen_udp/send socket addr port data), etc.")
|
||||
|
||||
(defn open
|
||||
"Opens a UDP socket. Returns {:ok socket} or {:error reason}.
|
||||
(gen_udp/open 0 [:binary {:active false}]) ;=> {:ok socket}"
|
||||
([port])
|
||||
([port opts]))
|
||||
|
||||
(defn send
|
||||
"Sends a UDP packet.
|
||||
(gen_udp/send socket \"localhost\" 4000 \"hello\")
|
||||
(gen_udp/send socket destination packet)"
|
||||
([socket destination packet])
|
||||
([socket host port packet]))
|
||||
|
||||
(defn recv
|
||||
"Receives a UDP packet. Returns {:ok {address port data}} or {:error reason}.
|
||||
(gen_udp/recv socket 0) ;=> {:ok {addr port data}}
|
||||
(gen_udp/recv socket 1024 5000) ;=> with timeout"
|
||||
([socket length])
|
||||
([socket length timeout]))
|
||||
|
||||
(defn close
|
||||
"Closes a UDP socket.
|
||||
(gen_udp/close socket) ;=> :ok"
|
||||
[socket])
|
||||
|
||||
(defn controlling-process
|
||||
"Transfers socket ownership to another process."
|
||||
[socket pid])
|
||||
@@ -0,0 +1,68 @@
|
||||
(ns inet
|
||||
"Erlang :inet module — network interface configuration.
|
||||
|
||||
In CljElixir: (inet/setopts socket opts), (inet/port socket), etc.")
|
||||
|
||||
(defn setopts
|
||||
"Sets socket options.
|
||||
(inet/setopts socket [{:active true}])
|
||||
(inet/setopts socket [{:packet :line}])"
|
||||
[socket opts])
|
||||
|
||||
(defn getopts
|
||||
"Gets socket options.
|
||||
(inet/getopts socket [:active :packet])"
|
||||
[socket opts])
|
||||
|
||||
(defn port
|
||||
"Returns the port number of a socket.
|
||||
(inet/port socket) ;=> {:ok 4000}"
|
||||
[socket])
|
||||
|
||||
(defn peername
|
||||
"Returns {address port} of the remote end.
|
||||
(inet/peername socket) ;=> {:ok {{127 0 0 1} 4000}}"
|
||||
[socket])
|
||||
|
||||
(defn sockname
|
||||
"Returns {address port} of the local end.
|
||||
(inet/sockname socket) ;=> {:ok {{0 0 0 0} 4000}}"
|
||||
[socket])
|
||||
|
||||
(defn getaddr
|
||||
"Resolves hostname to IP address.
|
||||
(inet/getaddr \"localhost\" :inet) ;=> {:ok {127 0 0 1}}"
|
||||
[hostname family])
|
||||
|
||||
(defn gethostname
|
||||
"Returns the hostname of the local machine.
|
||||
(inet/gethostname) ;=> {:ok \"myhost\"}"
|
||||
[])
|
||||
|
||||
(defn getifaddrs
|
||||
"Returns network interface addresses.
|
||||
(inet/getifaddrs) ;=> {:ok [...]}"
|
||||
[])
|
||||
|
||||
(defn parse-address
|
||||
"Parses an IP address string.
|
||||
(inet/parse-address \"192.168.1.1\") ;=> {:ok {192 168 1 1}}"
|
||||
[address])
|
||||
|
||||
(defn ntoa
|
||||
"Converts IP tuple to string.
|
||||
(inet/ntoa {192 168 1 1}) ;=> '192.168.1.1'"
|
||||
[ip])
|
||||
|
||||
(defn close
|
||||
"Closes a socket."
|
||||
[socket])
|
||||
|
||||
(defn controlling-process
|
||||
"Transfers socket control to another process."
|
||||
[socket pid])
|
||||
|
||||
(defn i
|
||||
"Prints information about all open sockets."
|
||||
([])
|
||||
([proto]))
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
(ns lists
|
||||
"Erlang :lists module — list processing functions.
|
||||
|
||||
In CljElixir: (lists/reverse lst), (lists/sort lst), etc.
|
||||
Lower-level than Enum; operates directly on Erlang lists.")
|
||||
|
||||
(defn reverse
|
||||
"Reverses a list.
|
||||
(lists/reverse [3 1 2]) ;=> [2 1 3]"
|
||||
([list])
|
||||
([list tail]))
|
||||
|
||||
(defn sort
|
||||
"Sorts a list using Erlang term ordering.
|
||||
(lists/sort [3 1 2]) ;=> [1 2 3]
|
||||
(lists/sort (fn [a b] (> a b)) [3 1 2]) ;=> [3 2 1]"
|
||||
([list])
|
||||
([fun list]))
|
||||
|
||||
(defn usort
|
||||
"Sorts and removes duplicates.
|
||||
(lists/usort [3 1 2 1]) ;=> [1 2 3]"
|
||||
([list])
|
||||
([fun list]))
|
||||
|
||||
(defn keysort
|
||||
"Sorts list of tuples by element at `n`-th position (1-based).
|
||||
(lists/keysort 1 [{:b 2} {:a 1}]) ;=> [{:a 1} {:b 2}]"
|
||||
[n tuple-list])
|
||||
|
||||
(defn keyfind
|
||||
"Finds first tuple where element at `n` matches `key`. Returns tuple or false.
|
||||
(lists/keyfind :a 1 [{:a 1} {:b 2}]) ;=> {:a 1}"
|
||||
[key n tuple-list])
|
||||
|
||||
(defn keystore
|
||||
"Replaces or appends tuple in list based on key at position `n`."
|
||||
[key n tuple-list new-tuple])
|
||||
|
||||
(defn keydelete
|
||||
"Deletes first tuple with matching key at position `n`."
|
||||
[key n tuple-list])
|
||||
|
||||
(defn keymember
|
||||
"Returns true if any tuple has `key` at position `n`."
|
||||
[key n tuple-list])
|
||||
|
||||
(defn keyreplace
|
||||
"Replaces first tuple with matching key at position `n`."
|
||||
[key n tuple-list new-tuple])
|
||||
|
||||
(defn keytake
|
||||
"Takes first tuple with matching key. Returns {value rest} or false."
|
||||
[key n tuple-list])
|
||||
|
||||
(defn map
|
||||
"Applies `fun` to each element, returns new list.
|
||||
(lists/map (fn [x] (* x 2)) [1 2 3]) ;=> [2 4 6]"
|
||||
[fun list])
|
||||
|
||||
(defn flatmap
|
||||
"Maps and flattens one level.
|
||||
(lists/flatmap (fn [x] [x x]) [1 2 3]) ;=> [1 1 2 2 3 3]"
|
||||
[fun list])
|
||||
|
||||
(defn filter
|
||||
"Returns elements for which `pred` returns true.
|
||||
(lists/filter (fn [x] (> x 2)) [1 2 3 4]) ;=> [3 4]"
|
||||
[pred list])
|
||||
|
||||
(defn filtermap
|
||||
"Filters and maps in one pass. `fun` returns true/false/{true, value}.
|
||||
(lists/filtermap (fn [x] (if (> x 2) {:true (* x 10)} false)) [1 2 3 4]) ;=> [30 40]"
|
||||
[fun list])
|
||||
|
||||
(defn foldl
|
||||
"Left fold.
|
||||
(lists/foldl (fn [elem acc] (+ acc elem)) 0 [1 2 3]) ;=> 6"
|
||||
[fun acc list])
|
||||
|
||||
(defn foldr
|
||||
"Right fold.
|
||||
(lists/foldr (fn [elem acc] (+ acc elem)) 0 [1 2 3]) ;=> 6"
|
||||
[fun acc list])
|
||||
|
||||
(defn foreach
|
||||
"Calls `fun` on each element for side effects. Returns :ok.
|
||||
(lists/foreach (fn [x] (IO/puts x)) [1 2 3])"
|
||||
[fun list])
|
||||
|
||||
(defn flatten
|
||||
"Flattens nested lists.
|
||||
(lists/flatten [[1 [2]] [3]]) ;=> [1 2 3]"
|
||||
([list])
|
||||
([list tail]))
|
||||
|
||||
(defn append
|
||||
"Appends lists.
|
||||
(lists/append [1 2] [3 4]) ;=> [1 2 3 4]
|
||||
(lists/append [[1] [2] [3]]) ;=> [1 2 3]"
|
||||
([list-of-lists])
|
||||
([list1 list2]))
|
||||
|
||||
(defn concat
|
||||
"Concatenates a list of things to a flat list."
|
||||
[list])
|
||||
|
||||
(defn merge
|
||||
"Merges sorted lists.
|
||||
(lists/merge [1 3 5] [2 4 6]) ;=> [1 2 3 4 5 6]"
|
||||
([list1 list2])
|
||||
([fun list1 list2])
|
||||
([list-of-lists]))
|
||||
|
||||
(defn zip
|
||||
"Zips two lists into a list of tuples.
|
||||
(lists/zip [1 2 3] [:a :b :c]) ;=> [{1 :a} {2 :b} {3 :c}]"
|
||||
[list1 list2])
|
||||
|
||||
(defn unzip
|
||||
"Unzips a list of tuples into two lists.
|
||||
(lists/unzip [{1 :a} {2 :b}]) ;=> {[1 2] [:a :b]}"
|
||||
[tuple-list])
|
||||
|
||||
(defn zipwith
|
||||
"Zips with a combining function.
|
||||
(lists/zipwith (fn [a b] (+ a b)) [1 2 3] [4 5 6]) ;=> [5 7 9]"
|
||||
[fun list1 list2])
|
||||
|
||||
(defn member
|
||||
"Returns true if `elem` is in `list`.
|
||||
(lists/member 2 [1 2 3]) ;=> true"
|
||||
[elem list])
|
||||
|
||||
(defn nth
|
||||
"Returns the `n`-th element (1-based).
|
||||
(lists/nth 2 [10 20 30]) ;=> 20"
|
||||
[n list])
|
||||
|
||||
(defn nthtail
|
||||
"Returns the tail starting at position `n` (1-based).
|
||||
(lists/nthtail 2 [10 20 30]) ;=> [30]"
|
||||
[n list])
|
||||
|
||||
(defn last
|
||||
"Returns the last element.
|
||||
(lists/last [1 2 3]) ;=> 3"
|
||||
[list])
|
||||
|
||||
(defn delete
|
||||
"Deletes first occurrence of `elem`.
|
||||
(lists/delete 2 [1 2 3 2]) ;=> [1 3 2]"
|
||||
[elem list])
|
||||
|
||||
(defn subtract
|
||||
"Subtracts elements of `list2` from `list1`.
|
||||
(lists/subtract [1 2 3 2] [2]) ;=> [1 3 2]"
|
||||
[list1 list2])
|
||||
|
||||
(defn duplicate
|
||||
"Creates a list with `elem` repeated `n` times.
|
||||
(lists/duplicate 3 :ok) ;=> [:ok :ok :ok]"
|
||||
[n elem])
|
||||
|
||||
(defn seq
|
||||
"Generates a sequence from `from` to `to`.
|
||||
(lists/seq 1 5) ;=> [1 2 3 4 5]
|
||||
(lists/seq 1 10 2) ;=> [1 3 5 7 9]"
|
||||
([from to])
|
||||
([from to step]))
|
||||
|
||||
(defn sum
|
||||
"Returns the sum of all elements."
|
||||
[list])
|
||||
|
||||
(defn max
|
||||
"Returns the maximum element."
|
||||
[list])
|
||||
|
||||
(defn min
|
||||
"Returns the minimum element."
|
||||
[list])
|
||||
|
||||
(defn prefix
|
||||
"Returns true if `list1` is a prefix of `list2`."
|
||||
[list1 list2])
|
||||
|
||||
(defn suffix
|
||||
"Returns true if `list1` is a suffix of `list2`."
|
||||
[list1 list2])
|
||||
|
||||
(defn splitwith
|
||||
"Splits list into two at the point where `pred` first returns false."
|
||||
[pred list])
|
||||
|
||||
(defn partition
|
||||
"Partitions list into two: elements satisfying `pred` and those that don't."
|
||||
[pred list])
|
||||
|
||||
(defn takewhile
|
||||
"Takes elements while `pred` returns true."
|
||||
[pred list])
|
||||
|
||||
(defn dropwhile
|
||||
"Drops elements while `pred` returns true."
|
||||
[pred list])
|
||||
|
||||
(defn droplast
|
||||
"Drops the last element of a list.
|
||||
(lists/droplast [1 2 3]) ;=> [1 2]"
|
||||
[list])
|
||||
|
||||
(defn sublist
|
||||
"Returns a sublist.
|
||||
(lists/sublist [1 2 3 4 5] 3) ;=> [1 2 3]
|
||||
(lists/sublist [1 2 3 4 5] 2 3) ;=> [2 3 4]"
|
||||
([list len])
|
||||
([list start len]))
|
||||
|
||||
(defn search
|
||||
"Searches for an element matching `pred`. Returns {:value elem} or false."
|
||||
[pred list])
|
||||
|
||||
(defn enumerate
|
||||
"Returns a list of {index, element} tuples.
|
||||
(lists/enumerate [\"a\" \"b\" \"c\"]) ;=> [{1 \"a\"} {2 \"b\"} {3 \"c\"}]"
|
||||
([list])
|
||||
([index list])
|
||||
([index step list]))
|
||||
|
||||
(defn all
|
||||
"Returns true if `pred` returns true for all elements."
|
||||
[pred list])
|
||||
|
||||
(defn any
|
||||
"Returns true if `pred` returns true for any element."
|
||||
[pred list])
|
||||
|
||||
(defn join
|
||||
"Joins list elements into a string (Elixir extension)."
|
||||
[list separator])
|
||||
|
||||
(defn map-foldl
|
||||
"Combined map and foldl."
|
||||
[fun acc list])
|
||||
|
||||
(defn map-foldr
|
||||
"Combined map and foldr."
|
||||
[fun acc list])
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
(ns maps
|
||||
"Erlang :maps module — map operations.
|
||||
|
||||
In CljElixir: (maps/get key map), (maps/put key value map), etc.
|
||||
Lower-level than Elixir's Map module.")
|
||||
|
||||
(defn get
|
||||
"Gets value for `key`. Raises if missing (or returns `default`).
|
||||
(maps/get :a {:a 1}) ;=> 1
|
||||
(maps/get :b {:a 1} :default) ;=> :default"
|
||||
([key map])
|
||||
([key map default]))
|
||||
|
||||
(defn find
|
||||
"Returns {:ok value} or :error.
|
||||
(maps/find :a {:a 1}) ;=> {:ok 1}"
|
||||
[key map])
|
||||
|
||||
(defn is-key
|
||||
"Returns true if `key` exists in `map`.
|
||||
(maps/is-key :a {:a 1}) ;=> true"
|
||||
[key map])
|
||||
|
||||
(defn keys
|
||||
"Returns all keys.
|
||||
(maps/keys {:a 1 :b 2}) ;=> [:a :b]"
|
||||
[map])
|
||||
|
||||
(defn values
|
||||
"Returns all values.
|
||||
(maps/values {:a 1 :b 2}) ;=> [1 2]"
|
||||
[map])
|
||||
|
||||
(defn size
|
||||
"Returns the number of entries.
|
||||
(maps/size {:a 1 :b 2}) ;=> 2"
|
||||
[map])
|
||||
|
||||
(defn put
|
||||
"Puts `key`/`value` into `map`.
|
||||
(maps/put :c 3 {:a 1 :b 2}) ;=> {:a 1 :b 2 :c 3}"
|
||||
[key value map])
|
||||
|
||||
(defn remove
|
||||
"Removes `key` from `map`.
|
||||
(maps/remove :a {:a 1 :b 2}) ;=> {:b 2}"
|
||||
[key map])
|
||||
|
||||
(defn update
|
||||
"Updates `key` by applying `fun` to current value. Raises if key missing.
|
||||
(maps/update :a (fn [v] (+ v 1)) {:a 1}) ;=> {:a 2}"
|
||||
[key fun map])
|
||||
|
||||
(defn update-with
|
||||
"Updates `key` with `fun`, using `init` if key is missing.
|
||||
(maps/update-with :a (fn [v] (+ v 1)) 0 {:a 1}) ;=> {:a 2}
|
||||
(maps/update-with :b (fn [v] (+ v 1)) 0 {:a 1}) ;=> {:a 1 :b 0}"
|
||||
([key fun map])
|
||||
([key fun init map]))
|
||||
|
||||
(defn merge
|
||||
"Merges two maps. `map2` values take precedence.
|
||||
(maps/merge {:a 1} {:b 2}) ;=> {:a 1 :b 2}"
|
||||
[map1 map2])
|
||||
|
||||
(defn merge-with
|
||||
"Merges with a conflict resolver.
|
||||
(maps/merge-with (fn [v1 v2] (+ v1 v2)) {:a 1} {:a 2}) ;=> {:a 3}"
|
||||
[fun map1 map2])
|
||||
|
||||
(defn intersect
|
||||
"Returns intersection of two maps."
|
||||
([map1 map2])
|
||||
([combiner map1 map2]))
|
||||
|
||||
(defn intersect-with
|
||||
"Intersects with a combining function."
|
||||
[combiner map1 map2])
|
||||
|
||||
(defn from-list
|
||||
"Creates a map from a list of {key, value} tuples.
|
||||
(maps/from-list [[:a 1] [:b 2]]) ;=> {:a 1 :b 2}"
|
||||
[list])
|
||||
|
||||
(defn from-keys
|
||||
"Creates a map from a list of keys, all with the same value.
|
||||
(maps/from-keys [:a :b :c] 0) ;=> {:a 0 :b 0 :c 0}"
|
||||
[keys value])
|
||||
|
||||
(defn to-list
|
||||
"Converts map to list of {key, value} tuples.
|
||||
(maps/to-list {:a 1 :b 2}) ;=> [[:a 1] [:b 2]]"
|
||||
[map])
|
||||
|
||||
(defn new
|
||||
"Creates an empty map.
|
||||
(maps/new) ;=> {}"
|
||||
[])
|
||||
|
||||
(defn map
|
||||
"Maps `fun` over map entries. `fun` receives (key, value).
|
||||
(maps/map (fn [k v] (* v 2)) {:a 1 :b 2}) ;=> {:a 2 :b 4}"
|
||||
[fun map])
|
||||
|
||||
(defn filter
|
||||
"Filters entries where `pred` returns true.
|
||||
(maps/filter (fn [k v] (> v 1)) {:a 1 :b 2 :c 3}) ;=> {:b 2 :c 3}"
|
||||
[pred map])
|
||||
|
||||
(defn filtermap
|
||||
"Filters and maps in one pass."
|
||||
[fun map])
|
||||
|
||||
(defn fold
|
||||
"Folds over map entries.
|
||||
(maps/fold (fn [k v acc] (+ acc v)) 0 {:a 1 :b 2}) ;=> 3"
|
||||
[fun acc map])
|
||||
|
||||
(defn foreach
|
||||
"Calls `fun` on each entry for side effects.
|
||||
(maps/foreach (fn [k v] (IO/puts (str k \": \" v))) my-map)"
|
||||
[fun map])
|
||||
|
||||
(defn with
|
||||
"Takes only the given `keys` from `map`.
|
||||
(maps/with [:a :c] {:a 1 :b 2 :c 3}) ;=> {:a 1 :c 3}"
|
||||
[keys map])
|
||||
|
||||
(defn without
|
||||
"Drops the given `keys` from `map`.
|
||||
(maps/without [:a] {:a 1 :b 2}) ;=> {:b 2}"
|
||||
[keys map])
|
||||
|
||||
(defn groups-from-list
|
||||
"Groups list elements by key function.
|
||||
(maps/groups-from-list (fn [x] (rem x 2)) [1 2 3 4]) ;=> {1 [1 3], 0 [2 4]}"
|
||||
([key-fun list])
|
||||
([key-fun value-fun list]))
|
||||
|
||||
(defn iterator
|
||||
"Returns a map iterator for lazy traversal.
|
||||
(maps/iterator my-map)"
|
||||
([map])
|
||||
([map order]))
|
||||
|
||||
(defn next
|
||||
"Advances a map iterator. Returns {key value iterator} or :none."
|
||||
[iterator])
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
(ns math
|
||||
"Erlang :math module — mathematical functions.
|
||||
|
||||
In CljElixir: (math/sqrt 2), (math/pow 2 10), etc.
|
||||
All functions return floats.")
|
||||
|
||||
(defn pi
|
||||
"Returns the value of pi.
|
||||
(math/pi) ;=> 3.141592653589793"
|
||||
[])
|
||||
|
||||
(defn e
|
||||
"Returns Euler's number.
|
||||
(math/e) ;=> 2.718281828459045"
|
||||
[])
|
||||
|
||||
(defn tau
|
||||
"Returns tau (2*pi).
|
||||
(math/tau) ;=> 6.283185307179586"
|
||||
[])
|
||||
|
||||
;; --- Powers & Roots ---
|
||||
|
||||
(defn pow
|
||||
"Returns `x` raised to `y`.
|
||||
(math/pow 2 10) ;=> 1024.0"
|
||||
[x y])
|
||||
|
||||
(defn sqrt
|
||||
"Returns the square root.
|
||||
(math/sqrt 4) ;=> 2.0"
|
||||
[x])
|
||||
|
||||
(defn exp
|
||||
"Returns e^x.
|
||||
(math/exp 1) ;=> 2.718281828459045"
|
||||
[x])
|
||||
|
||||
(defn log
|
||||
"Returns the natural logarithm (base e).
|
||||
(math/log 2.718281828459045) ;=> 1.0"
|
||||
[x])
|
||||
|
||||
(defn log2
|
||||
"Returns the base-2 logarithm.
|
||||
(math/log2 1024) ;=> 10.0"
|
||||
[x])
|
||||
|
||||
(defn log10
|
||||
"Returns the base-10 logarithm.
|
||||
(math/log10 1000) ;=> 3.0"
|
||||
[x])
|
||||
|
||||
;; --- Trigonometry ---
|
||||
|
||||
(defn sin
|
||||
"Returns the sine (radians).
|
||||
(math/sin (/ (math/pi) 2)) ;=> 1.0"
|
||||
[x])
|
||||
|
||||
(defn cos
|
||||
"Returns the cosine (radians).
|
||||
(math/cos 0) ;=> 1.0"
|
||||
[x])
|
||||
|
||||
(defn tan
|
||||
"Returns the tangent (radians).
|
||||
(math/tan 0) ;=> 0.0"
|
||||
[x])
|
||||
|
||||
(defn asin
|
||||
"Returns the arcsine (radians).
|
||||
(math/asin 1) ;=> 1.5707963267948966"
|
||||
[x])
|
||||
|
||||
(defn acos
|
||||
"Returns the arccosine (radians).
|
||||
(math/acos 1) ;=> 0.0"
|
||||
[x])
|
||||
|
||||
(defn atan
|
||||
"Returns the arctangent (radians).
|
||||
(math/atan 1) ;=> 0.7853981633974483"
|
||||
[x])
|
||||
|
||||
(defn atan2
|
||||
"Returns the two-argument arctangent (radians).
|
||||
(math/atan2 1 1) ;=> 0.7853981633974483"
|
||||
[y x])
|
||||
|
||||
;; --- Hyperbolic ---
|
||||
|
||||
(defn sinh
|
||||
"Returns the hyperbolic sine."
|
||||
[x])
|
||||
|
||||
(defn cosh
|
||||
"Returns the hyperbolic cosine."
|
||||
[x])
|
||||
|
||||
(defn tanh
|
||||
"Returns the hyperbolic tangent."
|
||||
[x])
|
||||
|
||||
(defn asinh
|
||||
"Returns the inverse hyperbolic sine."
|
||||
[x])
|
||||
|
||||
(defn acosh
|
||||
"Returns the inverse hyperbolic cosine."
|
||||
[x])
|
||||
|
||||
(defn atanh
|
||||
"Returns the inverse hyperbolic tangent."
|
||||
[x])
|
||||
|
||||
;; --- Rounding ---
|
||||
|
||||
(defn ceil
|
||||
"Returns the smallest integer >= x (as float).
|
||||
(math/ceil 3.2) ;=> 4.0"
|
||||
[x])
|
||||
|
||||
(defn floor
|
||||
"Returns the largest integer <= x (as float).
|
||||
(math/floor 3.8) ;=> 3.0"
|
||||
[x])
|
||||
|
||||
(defn fmod
|
||||
"Returns the floating-point remainder of x/y.
|
||||
(math/fmod 10.0 3.0) ;=> 1.0"
|
||||
[x y])
|
||||
|
||||
(defn erf
|
||||
"Returns the error function.
|
||||
(math/erf 1) ;=> 0.8427007929497149"
|
||||
[x])
|
||||
|
||||
(defn erfc
|
||||
"Returns the complementary error function."
|
||||
[x])
|
||||
@@ -0,0 +1,72 @@
|
||||
(ns os
|
||||
"Erlang :os module — operating system interface.
|
||||
|
||||
In CljElixir: (os/cmd \"ls\"), (os/type), etc.")
|
||||
|
||||
(defn cmd
|
||||
"Executes an OS command and returns the output as a charlist.
|
||||
(os/cmd \"ls -la\") ;=> 'total 42\\n...'"
|
||||
([command])
|
||||
([command opts]))
|
||||
|
||||
(defn type
|
||||
"Returns the OS type.
|
||||
(os/type) ;=> {:unix :darwin} or {:win32 :nt}"
|
||||
[])
|
||||
|
||||
(defn version
|
||||
"Returns the OS version.
|
||||
(os/version) ;=> {14 3 0}"
|
||||
[])
|
||||
|
||||
(defn getenv
|
||||
"Gets an environment variable. Returns charlist or false.
|
||||
(os/getenv \"HOME\") ;=> '/Users/ajet'
|
||||
(os/getenv \"MISSING\") ;=> false"
|
||||
([name])
|
||||
([name default]))
|
||||
|
||||
(defn putenv
|
||||
"Sets an environment variable.
|
||||
(os/putenv \"MY_VAR\" \"value\") ;=> true"
|
||||
[name value])
|
||||
|
||||
(defn unsetenv
|
||||
"Removes an environment variable.
|
||||
(os/unsetenv \"MY_VAR\") ;=> true"
|
||||
[name])
|
||||
|
||||
(defn getenv
|
||||
"Gets all environment variables as list of \"KEY=VALUE\" strings."
|
||||
[])
|
||||
|
||||
(defn timestamp
|
||||
"Returns OS timestamp as {megasecs secs microsecs}.
|
||||
(os/timestamp) ;=> {1709 123456 789012}"
|
||||
[])
|
||||
|
||||
(defn system-time
|
||||
"Returns OS system time.
|
||||
(os/system-time :millisecond)"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn perf-counter
|
||||
"Returns a performance counter value.
|
||||
(os/perf-counter :nanosecond)"
|
||||
([])
|
||||
([unit]))
|
||||
|
||||
(defn find-executable
|
||||
"Finds an executable in PATH. Returns path or false.
|
||||
(os/find-executable \"elixir\") ;=> '/usr/local/bin/elixir'"
|
||||
[name])
|
||||
|
||||
(defn getpid
|
||||
"Returns the OS process ID as a string.
|
||||
(os/getpid) ;=> \"12345\""
|
||||
[])
|
||||
|
||||
(defn set-signal
|
||||
"Sets OS signal handler."
|
||||
[signal action])
|
||||
@@ -0,0 +1,73 @@
|
||||
(ns timer
|
||||
"Erlang :timer module — timer utilities.
|
||||
|
||||
In CljElixir: (timer/sleep 1000), (timer/send-after 5000 pid :msg), etc.
|
||||
Note: for most uses, Process/send-after is preferred in Elixir.")
|
||||
|
||||
(defn sleep
|
||||
"Suspends the calling process for `time` milliseconds.
|
||||
(timer/sleep 1000) ;=> :ok (sleeps 1 second)
|
||||
(timer/sleep :infinity) ;=> blocks forever"
|
||||
[time])
|
||||
|
||||
(defn send-after
|
||||
"Sends `message` to `pid` after `time` milliseconds. Returns {:ok tref}.
|
||||
(timer/send-after 5000 (Process/self) :timeout)"
|
||||
([time pid message])
|
||||
([time message]))
|
||||
|
||||
(defn send-interval
|
||||
"Sends `message` to `pid` every `time` milliseconds. Returns {:ok tref}.
|
||||
(timer/send-interval 1000 (Process/self) :tick)"
|
||||
([time pid message])
|
||||
([time message]))
|
||||
|
||||
(defn apply-after
|
||||
"Applies `module:function(args)` after `time` milliseconds.
|
||||
(timer/apply-after 5000 IO :puts [\"delayed\"])"
|
||||
[time module function args])
|
||||
|
||||
(defn apply-interval
|
||||
"Applies `module:function(args)` every `time` milliseconds."
|
||||
[time module function args])
|
||||
|
||||
(defn apply-repeatedly
|
||||
"Applies `module:function(args)` repeatedly with `time` delay after each."
|
||||
[time module function args])
|
||||
|
||||
(defn cancel
|
||||
"Cancels a timer.
|
||||
(timer/cancel tref) ;=> {:ok :cancel}"
|
||||
[tref])
|
||||
|
||||
(defn tc
|
||||
"Times the execution of a function. Returns {time result} in microseconds.
|
||||
(timer/tc (fn [] (fib 30))) ;=> {12345 832040}
|
||||
(timer/tc Enum :sort [[3 1 2]]) ;=> {microsecs [1 2 3]}"
|
||||
([fun])
|
||||
([fun args])
|
||||
([module function args]))
|
||||
|
||||
(defn now-diff
|
||||
"Returns time difference in microseconds between two erlang:now() tuples."
|
||||
[t2 t1])
|
||||
|
||||
(defn seconds
|
||||
"Converts seconds to milliseconds.
|
||||
(timer/seconds 5) ;=> 5000"
|
||||
[secs])
|
||||
|
||||
(defn minutes
|
||||
"Converts minutes to milliseconds.
|
||||
(timer/minutes 5) ;=> 300000"
|
||||
[mins])
|
||||
|
||||
(defn hours
|
||||
"Converts hours to milliseconds.
|
||||
(timer/hours 1) ;=> 3600000"
|
||||
[hours])
|
||||
|
||||
(defn hms
|
||||
"Converts hours, minutes, seconds to milliseconds.
|
||||
(timer/hms 1 30 0) ;=> 5400000"
|
||||
[hours minutes seconds])
|
||||
@@ -245,6 +245,92 @@ defmodule CljElixir.NReplTest do
|
||||
[response] = Handler.handle(%{"op" => "bogus", "id" => "1"}, manager)
|
||||
assert "unknown-op" in response["status"]
|
||||
end
|
||||
|
||||
test "eval returns actual ns after ns declaration", %{manager: manager} do
|
||||
[clone_resp] = Handler.handle(%{"op" => "clone", "id" => "1"}, manager)
|
||||
session = clone_resp["new-session"]
|
||||
|
||||
# Default ns is "user"
|
||||
responses =
|
||||
Handler.handle(
|
||||
%{"op" => "eval", "code" => "(+ 1 2)", "session" => session, "id" => "2"},
|
||||
manager
|
||||
)
|
||||
|
||||
values = Enum.filter(responses, &Map.has_key?(&1, "value"))
|
||||
assert hd(values)["ns"] == "user"
|
||||
|
||||
# After ns declaration, ns should update
|
||||
responses =
|
||||
Handler.handle(
|
||||
%{
|
||||
"op" => "eval",
|
||||
"code" => "(ns NReplNsTest)\n(defn hi [] :hey)",
|
||||
"session" => session,
|
||||
"id" => "3"
|
||||
},
|
||||
manager
|
||||
)
|
||||
|
||||
values = Enum.filter(responses, &Map.has_key?(&1, "value"))
|
||||
assert hd(values)["ns"] == "NReplNsTest"
|
||||
end
|
||||
|
||||
test "incremental def in ns via nrepl", %{manager: manager} do
|
||||
[clone_resp] = Handler.handle(%{"op" => "clone", "id" => "1"}, manager)
|
||||
session = clone_resp["new-session"]
|
||||
|
||||
# Set up namespace
|
||||
Handler.handle(
|
||||
%{
|
||||
"op" => "eval",
|
||||
"code" => "(ns NReplIncrTest)\n(defn foo [] :original)",
|
||||
"session" => session,
|
||||
"id" => "2"
|
||||
},
|
||||
manager
|
||||
)
|
||||
|
||||
# Add a new function without ns
|
||||
Handler.handle(
|
||||
%{
|
||||
"op" => "eval",
|
||||
"code" => "(defn bar [] :added)",
|
||||
"session" => session,
|
||||
"id" => "3"
|
||||
},
|
||||
manager
|
||||
)
|
||||
|
||||
# Both should work
|
||||
responses =
|
||||
Handler.handle(
|
||||
%{
|
||||
"op" => "eval",
|
||||
"code" => "(NReplIncrTest/bar)",
|
||||
"session" => session,
|
||||
"id" => "4"
|
||||
},
|
||||
manager
|
||||
)
|
||||
|
||||
values = Enum.filter(responses, &Map.has_key?(&1, "value"))
|
||||
assert hd(values)["value"] == ":added"
|
||||
|
||||
responses =
|
||||
Handler.handle(
|
||||
%{
|
||||
"op" => "eval",
|
||||
"code" => "(NReplIncrTest/foo)",
|
||||
"session" => session,
|
||||
"id" => "5"
|
||||
},
|
||||
manager
|
||||
)
|
||||
|
||||
values = Enum.filter(responses, &Map.has_key?(&1, "value"))
|
||||
assert hd(values)["value"] == ":original"
|
||||
end
|
||||
end
|
||||
|
||||
# --- TCP Integration Test ---
|
||||
|
||||
@@ -86,6 +86,139 @@ defmodule CljElixir.REPLTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "ns tracking" do
|
||||
test "ns sets current namespace" do
|
||||
state = REPL.new()
|
||||
assert REPL.current_ns(state) == "user"
|
||||
|
||||
{:ok, _, state2} = REPL.eval("(ns ReplNsTest1)", state)
|
||||
assert state2.current_ns == "ReplNsTest1"
|
||||
assert REPL.current_ns(state2) == "ReplNsTest1"
|
||||
end
|
||||
|
||||
test "ns with defn creates module" do
|
||||
state = REPL.new()
|
||||
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTest2)
|
||||
(defn greet [name] (str "hello " name))
|
||||
""", state)
|
||||
|
||||
assert state2.current_ns == "ReplNsTest2"
|
||||
{:ok, result, _} = REPL.eval("(ReplNsTest2/greet \"world\")", state2)
|
||||
assert result == "\"hello world\""
|
||||
end
|
||||
|
||||
test "bare defn in active ns updates module" do
|
||||
state = REPL.new()
|
||||
|
||||
# Set up initial module
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTest3)
|
||||
(defn hello [] :hi)
|
||||
""", state)
|
||||
|
||||
# Add a new function without ns
|
||||
{:ok, result, state3} = REPL.eval("(defn world [] :earth)", state2)
|
||||
assert result == "#'ReplNsTest3/world"
|
||||
|
||||
# Both functions should work
|
||||
{:ok, r1, _} = REPL.eval("(ReplNsTest3/hello)", state3)
|
||||
assert r1 == ":hi"
|
||||
{:ok, r2, _} = REPL.eval("(ReplNsTest3/world)", state3)
|
||||
assert r2 == ":earth"
|
||||
end
|
||||
|
||||
test "redefine function in active ns" do
|
||||
state = REPL.new()
|
||||
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTest4)
|
||||
(defn greet [] "v1")
|
||||
""", state)
|
||||
|
||||
{:ok, r1, _} = REPL.eval("(ReplNsTest4/greet)", state2)
|
||||
assert r1 == "\"v1\""
|
||||
|
||||
# Redefine greet
|
||||
{:ok, _, state3} = REPL.eval("(defn greet [] \"v2\")", state2)
|
||||
|
||||
{:ok, r2, _} = REPL.eval("(ReplNsTest4/greet)", state3)
|
||||
assert r2 == "\"v2\""
|
||||
end
|
||||
|
||||
test "bare defn without ns evals plain" do
|
||||
state = REPL.new()
|
||||
# No namespace set — def outside module should fail
|
||||
{:error, _, _} = REPL.eval("(defn orphan [] :lost)", state)
|
||||
end
|
||||
|
||||
test "expressions eval normally in ns context" do
|
||||
state = REPL.new()
|
||||
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTest5)
|
||||
(defn add [a b] (+ a b))
|
||||
""", state)
|
||||
|
||||
# Expression (no def) goes through plain eval
|
||||
{:ok, result, _} = REPL.eval("(ReplNsTest5/add 3 4)", state2)
|
||||
assert result == "7"
|
||||
end
|
||||
|
||||
test "mixed defs and exprs in ns context" do
|
||||
state = REPL.new()
|
||||
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTest6)
|
||||
(defn double [x] (* x 2))
|
||||
""", state)
|
||||
|
||||
# Eval def + expression together
|
||||
{:ok, result, state3} =
|
||||
REPL.eval("""
|
||||
(defn triple [x] (* x 3))
|
||||
(ReplNsTest6/triple 5)
|
||||
""", state2)
|
||||
|
||||
# Result should be the expression value
|
||||
assert result == "15"
|
||||
|
||||
# Both functions should work
|
||||
{:ok, r1, _} = REPL.eval("(ReplNsTest6/double 4)", state3)
|
||||
assert r1 == "8"
|
||||
end
|
||||
|
||||
test "switching namespace" do
|
||||
state = REPL.new()
|
||||
|
||||
{:ok, _, state2} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTestA)
|
||||
(defn a-fn [] :from-a)
|
||||
""", state)
|
||||
|
||||
{:ok, _, state3} =
|
||||
REPL.eval("""
|
||||
(ns ReplNsTestB)
|
||||
(defn b-fn [] :from-b)
|
||||
""", state2)
|
||||
|
||||
assert state3.current_ns == "ReplNsTestB"
|
||||
|
||||
# Both modules still work
|
||||
{:ok, r1, _} = REPL.eval("(ReplNsTestA/a-fn)", state3)
|
||||
assert r1 == ":from-a"
|
||||
{:ok, r2, _} = REPL.eval("(ReplNsTestB/b-fn)", state3)
|
||||
assert r2 == ":from-b"
|
||||
end
|
||||
end
|
||||
|
||||
describe "REPL.balanced?" do
|
||||
test "balanced parens" do
|
||||
assert REPL.balanced?("(+ 1 2)")
|
||||
|
||||
Reference in New Issue
Block a user