{
"version": 1,
"entries": [
{
"name": "agent/max-turns",
"module": "agent",
"summary": "Return the maximum number of tool-execution rounds the agent will run before stopping.",
"params": [
{
"name": "agent",
"type": "agent"
}
],
"returns": "int",
"examples": [
"(agent/max-turns my-agent) ; => 10"
],
"body": "Return the maximum number of tool-execution rounds the agent will run before stopping.\n\n```sema\n(agent/max-turns my-agent) ; => 10\n```"
},
{
"name": "agent/model",
"module": "agent",
"summary": "Return the model name the agent uses.",
"params": [
{
"name": "agent",
"type": "agent"
}
],
"returns": "string",
"examples": [
"(agent/model my-agent) ; => \"claude-sonnet-4\""
],
"body": "Return the model name the agent uses.\n\n```sema\n(agent/model my-agent) ; => \"claude-sonnet-4\"\n```"
},
{
"name": "agent/name",
"module": "agent",
"summary": "Return the agent's name.",
"params": [
{
"name": "agent",
"type": "agent"
}
],
"returns": "string",
"examples": [
"(agent/name my-agent) ; => \"weather-bot\""
],
"body": "Return the agent's name.\n\n```sema\n(agent/name my-agent) ; => \"weather-bot\"\n```"
},
{
"name": "agent/run",
"module": "agent",
"summary": "Run an agent on a user message, executing its tools in a loop up to the agent's `:max-turns`. With two arguments it returns the final reply string. With an opts map it returns `{:response ... :messages ...}`; opts accepts `:messages` (prior history) and `:on-tool-call` (a callback invoked for each tool call). Each turn is generated with a fixed `max-tokens` of 4096.",
"params": [
{
"name": "agent",
"type": "agent"
},
{
"name": "message",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(agent/run my-agent \"Look up the weather in Oslo and summarize it.\")"
],
"body": "Run an agent on a user message, executing its tools in a loop up to the agent's `:max-turns`. With two arguments it returns the final reply string. With an opts map it returns `{:response ... :messages ...}`; opts accepts `:messages` (prior history) and `:on-tool-call` (a callback invoked for each tool call). Each turn is generated with a fixed `max-tokens` of 4096.\n\n```sema\n(agent/run my-agent \"Look up the weather in Oslo and summarize it.\")\n```"
},
{
"name": "agent/system",
"module": "agent",
"summary": "Return the agent's system prompt.",
"params": [
{
"name": "agent",
"type": "agent"
}
],
"returns": "string",
"examples": [
"(agent/system my-agent)"
],
"body": "Return the agent's system prompt.\n\n```sema\n(agent/system my-agent)\n```"
},
{
"name": "agent/tools",
"module": "agent",
"summary": "Return the agent's tools as a list of tool values.",
"params": [
{
"name": "agent",
"type": "agent"
}
],
"returns": "list",
"examples": [
"(agent/tools my-agent)"
],
"body": "Return the agent's tools as a list of tool values.\n\n```sema\n(agent/tools my-agent)\n```"
},
{
"name": "bytevector",
"module": "bytevectors",
"section": "Construction",
"summary": "Create a bytevector from byte values.",
"examples": [
"(bytevector 1 2 3) ; => #u8(1 2 3)\n(bytevector) ; => #u8()"
],
"body": "Create a bytevector from byte values.\n\n```sema\n(bytevector 1 2 3) ; => #u8(1 2 3)\n(bytevector) ; => #u8()\n```"
},
{
"name": "bytevector->list",
"module": "bytevectors",
"summary": "Convert a bytevector into a list of byte values (each an int in 0..255). Legacy Scheme name; `bytevector/to-list` is the namespaced alias.",
"params": [
{
"name": "bv",
"type": "bytevector"
}
],
"returns": "list",
"examples": [
"(bytevector->list #u8(65 66)) ; => (65 66)"
],
"body": "Convert a bytevector into a list of byte values (each an int in 0..255). Legacy Scheme name; `bytevector/to-list` is the namespaced alias.\n\n```sema\n(bytevector->list #u8(65 66)) ; => (65 66)\n```"
},
{
"name": "bytevector-append",
"module": "bytevectors",
"summary": "Concatenate any number of bytevectors into a new bytevector. Legacy Scheme name; `bytevector/append` is the namespaced alias.",
"params": [
{
"name": "bvs",
"type": "bytevector"
}
],
"returns": "bytevector",
"examples": [
"(bytevector-append #u8(1 2) #u8(3 4)) ; => #u8(1 2 3 4)"
],
"body": "Concatenate any number of bytevectors into a new bytevector. Legacy Scheme name; `bytevector/append` is the namespaced alias.\n\n```sema\n(bytevector-append #u8(1 2) #u8(3 4)) ; => #u8(1 2 3 4)\n```"
},
{
"name": "bytevector-copy",
"module": "bytevectors",
"summary": "Copy a bytevector, optionally restricting to the half-open range `start..end` (default `start` is `0`, `end` is the length). Signals an error if the range is out of bounds. Legacy Scheme name; `bytevector/copy` is the namespaced alias.",
"params": [
{
"name": "bv",
"type": "bytevector"
},
{
"name": "start",
"type": "int"
},
{
"name": "end",
"type": "int"
}
],
"returns": "bytevector",
"examples": [
"(bytevector-copy #u8(1 2 3 4 5) 1 4) ; => #u8(2 3 4)"
],
"body": "Copy a bytevector, optionally restricting to the half-open range `start..end` (default `start` is `0`, `end` is the length). Signals an error if the range is out of bounds. Legacy Scheme name; `bytevector/copy` is the namespaced alias.\n\n```sema\n(bytevector-copy #u8(1 2 3 4 5) 1 4) ; => #u8(2 3 4)\n```"
},
{
"name": "bytevector-length",
"module": "bytevectors",
"summary": "Return the number of bytes in a bytevector. Legacy Scheme name; `bytevector/length` is the namespaced alias.",
"params": [
{
"name": "bv",
"type": "bytevector"
}
],
"returns": "int",
"examples": [
"(bytevector-length #u8(1 2 3)) ; => 3"
],
"body": "Return the number of bytes in a bytevector. Legacy Scheme name; `bytevector/length` is the namespaced alias.\n\n```sema\n(bytevector-length #u8(1 2 3)) ; => 3\n```"
},
{
"name": "bytevector-u8-ref",
"module": "bytevectors",
"summary": "Return the byte (0..255) at `index` in a bytevector. Signals an error if `index` is out of range.",
"params": [
{
"name": "bv",
"type": "bytevector"
},
{
"name": "index",
"type": "int"
}
],
"returns": "int",
"examples": [
"(bytevector-u8-ref #u8(10 20 30) 1) ; => 20"
],
"body": "Return the byte (0..255) at `index` in a bytevector. Signals an error if `index` is out of range.\n\n```sema\n(bytevector-u8-ref #u8(10 20 30) 1) ; => 20\n```"
},
{
"name": "bytevector-u8-set!",
"module": "bytevectors",
"summary": "Return a new bytevector with the byte at `index` set to `byte` (0..255). Uses copy-on-write — the original bytevector is unchanged. Signals an error if `index` is out of range or `byte` is outside 0..255.",
"params": [
{
"name": "bv",
"type": "bytevector"
},
{
"name": "index",
"type": "int"
},
{
"name": "byte",
"type": "int"
}
],
"returns": "bytevector",
"examples": [
"(bytevector-u8-set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)"
],
"body": "Return a new bytevector with the byte at `index` set to `byte` (0..255). Uses copy-on-write — the original bytevector is unchanged. Signals an error if `index` is out of range or `byte` is outside 0..255.\n\n```sema\n(bytevector-u8-set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)\n```"
},
{
"name": "bytevector/append",
"module": "bytevectors",
"section": "Copy & Append",
"summary": "Concatenate bytevectors.",
"examples": [
"(bytevector/append #u8(1 2) #u8(3 4)) ; => #u8(1 2 3 4)"
],
"body": "Concatenate bytevectors.\n\n```sema\n(bytevector/append #u8(1 2) #u8(3 4)) ; => #u8(1 2 3 4)\n```"
},
{
"name": "bytevector/copy",
"module": "bytevectors",
"section": "Copy & Append",
"summary": "Copy a slice of a bytevector. `(bytevector/copy bv start end)`.",
"examples": [
"(bytevector/copy #u8(1 2 3 4 5) 1 3) ; => #u8(2 3)"
],
"body": "Copy a slice of a bytevector. `(bytevector/copy bv start end)`.\n\n```sema\n(bytevector/copy #u8(1 2 3 4 5) 1 3) ; => #u8(2 3)\n```"
},
{
"name": "bytevector/from-list",
"module": "bytevectors",
"summary": "Namespaced alias for `list->bytevector`. Convert a list of byte values into a bytevector. Each element must be an int in the range 0..255.",
"params": [
{
"name": "bytes",
"type": "list"
}
],
"returns": "bytevector",
"examples": [
"(bytevector/from-list '(1 2 3)) ; => #u8(1 2 3)"
],
"body": "Namespaced alias for `list->bytevector`. Convert a list of byte values into a bytevector. Each element must be an int in the range 0..255.\n\n```sema\n(bytevector/from-list '(1 2 3)) ; => #u8(1 2 3)\n```"
},
{
"name": "bytevector/length",
"module": "bytevectors",
"section": "Access & Mutation",
"summary": "Return the length of a bytevector.",
"examples": [
"(bytevector/length #u8(1 2 3)) ; => 3\n(bytevector/length #u8()) ; => 0"
],
"body": "Return the length of a bytevector.\n\n```sema\n(bytevector/length #u8(1 2 3)) ; => 3\n(bytevector/length #u8()) ; => 0\n```"
},
{
"name": "bytevector/make",
"module": "bytevectors",
"summary": "Namespaced alias for `make-bytevector`. Create a bytevector of `size` bytes, each initialized to `fill` (default `0`). `size` must be non-negative and `fill` must be in the range 0..255.",
"params": [
{
"name": "size",
"type": "int"
},
{
"name": "fill",
"type": "int"
}
],
"returns": "bytevector",
"examples": [
"(bytevector/make 4) ; => #u8(0 0 0 0)\n(bytevector/make 3 255) ; => #u8(255 255 255)"
],
"body": "Namespaced alias for `make-bytevector`. Create a bytevector of `size` bytes, each initialized to `fill` (default `0`). `size` must be non-negative and `fill` must be in the range 0..255.\n\n```sema\n(bytevector/make 4) ; => #u8(0 0 0 0)\n(bytevector/make 3 255) ; => #u8(255 255 255)\n```"
},
{
"name": "bytevector/new",
"module": "bytevectors",
"section": "Construction",
"summary": "Create a bytevector of a given length, optionally filled with a value.",
"examples": [
"(bytevector/new 4) ; => #u8(0 0 0 0)\n(bytevector/new 3 255) ; => #u8(255 255 255)"
],
"body": "Create a bytevector of a given length, optionally filled with a value.\n\n```sema\n(bytevector/new 4) ; => #u8(0 0 0 0)\n(bytevector/new 3 255) ; => #u8(255 255 255)\n```"
},
{
"name": "bytevector/ref",
"module": "bytevectors",
"section": "Access & Mutation",
"summary": "Return the byte at a given index.",
"examples": [
"(bytevector/ref #u8(10 20 30) 1) ; => 20\n(bytevector/ref #u8(10 20 30) 0) ; => 10"
],
"body": "Return the byte at a given index.\n\n```sema\n(bytevector/ref #u8(10 20 30) 1) ; => 20\n(bytevector/ref #u8(10 20 30) 0) ; => 10\n```"
},
{
"name": "bytevector/set!",
"module": "bytevectors",
"section": "Access & Mutation",
"summary": "Set the byte at a given index. Uses copy-on-write — the original bytevector is unchanged.",
"examples": [
"(bytevector/set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)"
],
"body": "Set the byte at a given index. Uses copy-on-write — the original bytevector is unchanged.\n\n```sema\n(bytevector/set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)\n```"
},
{
"name": "bytevector/to-list",
"module": "bytevectors",
"section": "List Conversion",
"summary": "Convert a bytevector to a list of integers.",
"examples": [
"(bytevector/to-list #u8(65 66)) ; => (65 66)"
],
"body": "Convert a bytevector to a list of integers.\n\n```sema\n(bytevector/to-list #u8(65 66)) ; => (65 66)\n```"
},
{
"name": "bytevector/u8-ref",
"module": "bytevectors",
"summary": "Namespaced alias for `bytevector-u8-ref`. Return the byte (0..255) at `index` in a bytevector. Signals an error if `index` is out of range.",
"params": [
{
"name": "bv",
"type": "bytevector"
},
{
"name": "index",
"type": "int"
}
],
"returns": "int",
"examples": [
"(bytevector/u8-ref #u8(10 20 30) 1) ; => 20"
],
"body": "Namespaced alias for `bytevector-u8-ref`. Return the byte (0..255) at `index` in a bytevector. Signals an error if `index` is out of range.\n\n```sema\n(bytevector/u8-ref #u8(10 20 30) 1) ; => 20\n```"
},
{
"name": "bytevector/u8-set!",
"module": "bytevectors",
"summary": "Namespaced alias for `bytevector-u8-set!`. Return a new bytevector with the byte at `index` set to `byte` (0..255). Uses copy-on-write — the original bytevector is unchanged. Signals an error if `index` is out of range or `byte` is outside 0..255.",
"params": [
{
"name": "bv",
"type": "bytevector"
},
{
"name": "index",
"type": "int"
},
{
"name": "byte",
"type": "int"
}
],
"returns": "bytevector",
"examples": [
"(bytevector/u8-set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)"
],
"body": "Namespaced alias for `bytevector-u8-set!`. Return a new bytevector with the byte at `index` set to `byte` (0..255). Uses copy-on-write — the original bytevector is unchanged. Signals an error if `index` is out of range or `byte` is outside 0..255.\n\n```sema\n(bytevector/u8-set! #u8(1 2 3) 0 9) ; => #u8(9 2 3)\n```"
},
{
"name": "list->bytevector",
"module": "bytevectors",
"summary": "Convert a list of byte values into a bytevector. Each element must be an int in the range 0..255. Legacy Scheme name; `bytevector/from-list` and `list/to-bytevector` are the namespaced aliases.",
"params": [
{
"name": "bytes",
"type": "list"
}
],
"returns": "bytevector",
"examples": [
"(list->bytevector '(1 2 3)) ; => #u8(1 2 3)"
],
"body": "Convert a list of byte values into a bytevector. Each element must be an int in the range 0..255. Legacy Scheme name; `bytevector/from-list` and `list/to-bytevector` are the namespaced aliases.\n\n```sema\n(list->bytevector '(1 2 3)) ; => #u8(1 2 3)\n```"
},
{
"name": "list/to-bytevector",
"module": "bytevectors",
"section": "List Conversion",
"summary": "Convert a list of integers to a bytevector.",
"examples": [
"(list/to-bytevector '(1 2 3)) ; => #u8(1 2 3)"
],
"body": "Convert a list of integers to a bytevector.\n\n```sema\n(list/to-bytevector '(1 2 3)) ; => #u8(1 2 3)\n```"
},
{
"name": "make-bytevector",
"module": "bytevectors",
"summary": "Create a bytevector of `size` bytes, each initialized to `fill` (default `0`). `size` must be non-negative and `fill` must be in the range 0..255.",
"params": [
{
"name": "size",
"type": "int"
},
{
"name": "fill",
"type": "int"
}
],
"returns": "bytevector",
"examples": [
"(make-bytevector 4) ; => #u8(0 0 0 0)\n(make-bytevector 3 255) ; => #u8(255 255 255)"
],
"body": "Create a bytevector of `size` bytes, each initialized to `fill` (default `0`). `size` must be non-negative and `fill` must be in the range 0..255.\n\n```sema\n(make-bytevector 4) ; => #u8(0 0 0 0)\n(make-bytevector 3 255) ; => #u8(255 255 255)\n```"
},
{
"name": "string/to-utf8",
"aliases": [
"string->utf8"
],
"module": "bytevectors",
"section": "String Conversion",
"summary": "Encode a string as a UTF-8 bytevector.",
"examples": [
"(string/to-utf8 \"hi\") ; => #u8(104 105)\n(string/to-utf8 \"Hello\") ; => #u8(72 101 108 108 111)"
],
"body": "Encode a string as a UTF-8 bytevector.\n\n```sema\n(string/to-utf8 \"hi\") ; => #u8(104 105)\n(string/to-utf8 \"Hello\") ; => #u8(72 101 108 108 111)\n```"
},
{
"name": "utf8/to-string",
"aliases": [
"utf8->string"
],
"module": "bytevectors",
"section": "String Conversion",
"summary": "Decode a bytevector as a UTF-8 string.",
"examples": [
"(utf8/to-string #u8(104 105)) ; => \"hi\"\n(utf8/to-string #u8(72 101 108)) ; => \"Hel\""
],
"body": "Decode a bytevector as a UTF-8 string.\n\n```sema\n(utf8/to-string #u8(104 105)) ; => \"hi\"\n(utf8/to-string #u8(72 101 108)) ; => \"Hel\"\n```"
},
{
"name": "async/all",
"module": "concurrency",
"section": "Promises",
"summary": "Run all promises to completion and return a list of their results. Takes a list or vector of promises.",
"examples": [
"(async/all promises) → list",
"(let ((p1 (async 10))\n (p2 (async 20))\n (p3 (async 30)))\n (async/all (list p1 p2 p3))) ; => (10 20 30)"
],
"body": "```sema\n(async/all promises) → list\n```\n\nRun all promises to completion and return a list of their results. Takes a list or vector of promises.\n\n```sema\n(let ((p1 (async 10))\n (p2 (async 20))\n (p3 (async 30)))\n (async/all (list p1 p2 p3))) ; => (10 20 30)\n```"
},
{
"name": "async/await",
"module": "concurrency",
"section": "Promises",
"summary": "Wait for a promise to resolve. Inside an async task, yields to the scheduler. At the top level, runs the scheduler inline until the promise resolves. Raises an error if the promise was rejected.",
"examples": [
"(async/await promise) → value"
],
"body": "```sema\n(async/await promise) → value\n```\n\nWait for a promise to resolve. Inside an async task, yields to the scheduler. At the top level, runs the scheduler inline until the promise resolves. Raises an error if the promise was rejected."
},
{
"name": "async/cancel",
"module": "concurrency",
"section": "Promises",
"summary": "Request cancellation of a spawned task. Returns `#t` if the call actually transitioned the promise into the `Cancelled` state, `#f` if there was nothing to cancel — the promise was already terminal (resolved, rejected, previously cancelled) or was never spawned in the first place (e.g. created via `async/resolved`).",
"examples": [
"(async/cancel promise) → bool",
"(async/cancel (async/resolved 1)) ;; => #f (never spawned)\n(let ((p (async 42))) (await p) (async/cancel p)) ;; => #f (already resolved)\n(let ((p (async (async/sleep 100)))) (async/cancel p)) ;; => #t"
],
"body": "```sema\n(async/cancel promise) → bool\n```\n\nRequest cancellation of a spawned task. Returns `#t` if the call actually transitioned the promise into the `Cancelled` state, `#f` if there was nothing to cancel — the promise was already terminal (resolved, rejected, previously cancelled) or was never spawned in the first place (e.g. created via `async/resolved`).\n\nCancellation is best-effort and never errors. The next time the task hits a yield point, it transitions to `Cancelled`; subsequent `(await p)` raises `\"async/await: task was cancelled\"` (distinct from a normal rejection).\n\n```sema\n(async/cancel (async/resolved 1)) ;; => #f (never spawned)\n(let ((p (async 42))) (await p) (async/cancel p)) ;; => #f (already resolved)\n(let ((p (async (async/sleep 100)))) (async/cancel p)) ;; => #t\n```"
},
{
"name": "async/cancelled?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` if `promise` is in the `Cancelled` state — distinct from `async/rejected?`. Matches the state variant directly rather than the rejection message, so a user `(async/rejected \"cancelled\")` no longer aliases:",
"examples": [
"(async/cancelled? promise) → bool",
"(async/cancelled? (async/rejected \"cancelled\")) ;; => #f"
],
"body": "```sema\n(async/cancelled? promise) → bool\n```\n\n`#t` if `promise` is in the `Cancelled` state — distinct from `async/rejected?`. Matches the state variant directly rather than the rejection message, so a user `(async/rejected \"cancelled\")` no longer aliases:\n\n```sema\n(async/cancelled? (async/rejected \"cancelled\")) ;; => #f\n```"
},
{
"name": "async/forced?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` if a lazy promise (created with `delay`) has already been forced and cached its value, `#f` if it has not yet been evaluated. Canonical slash-namespaced alias of `promise-forced?`. Errors if the argument is not a promise.",
"params": [
{
"name": "promise",
"type": "promise"
}
],
"returns": "bool",
"examples": [
"(define p (delay (+ 1 2)))\n(async/forced? p) ; => #f\n(force p)\n(async/forced? p) ; => #t"
],
"body": "`#t` if a lazy promise (created with `delay`) has already been forced and cached its value, `#f` if it has not yet been evaluated. Canonical slash-namespaced alias of `promise-forced?`. Errors if the argument is not a promise.\n\n```sema\n(define p (delay (+ 1 2)))\n(async/forced? p) ; => #f\n(force p)\n(async/forced? p) ; => #t\n```"
},
{
"name": "async/pending?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` if `promise` is still pending (has not yet resolved, rejected, or been cancelled), `#f` otherwise. Errors if the argument is not a promise.",
"params": [
{
"name": "promise",
"type": "promise"
}
],
"returns": "bool",
"examples": [
"(async/pending? (async (do-work))) ; => #t (before the scheduler runs)"
],
"body": "`#t` if `promise` is still pending (has not yet resolved, rejected, or been cancelled), `#f` otherwise. Errors if the argument is not a promise.\n\n```sema\n(async/pending? (async (do-work))) ; => #t (before the scheduler runs)\n```"
},
{
"name": "async/promise?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` if `value` is an async promise (e.g. one returned by `async/spawn`, `async/resolved`, or `async/rejected`), `#f` otherwise.",
"params": [
{
"name": "value"
}
],
"returns": "bool",
"examples": [
"(async/promise? (async/resolved 42)) ; => #t\n(async/promise? 42) ; => #f"
],
"body": "`#t` if `value` is an async promise (e.g. one returned by `async/spawn`, `async/resolved`, or `async/rejected`), `#f` otherwise.\n\n```sema\n(async/promise? (async/resolved 42)) ; => #t\n(async/promise? 42) ; => #f\n```"
},
{
"name": "async/race",
"module": "concurrency",
"section": "Promises",
"summary": "Return the value of the first promise to resolve. Takes a list or vector of promises.",
"examples": [
"(async/race promises) → value"
],
"body": "```sema\n(async/race promises) → value\n```\n\nReturn the value of the first promise to resolve. Takes a list or vector of promises."
},
{
"name": "async/rejected",
"module": "concurrency",
"section": "Promises",
"summary": "Create an already-rejected promise with `message`.",
"examples": [
"(async/rejected message) → async-promise"
],
"body": "```sema\n(async/rejected message) → async-promise\n```\n\nCreate an already-rejected promise with `message`."
},
{
"name": "async/rejected?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` exactly when `promise` is in the `Rejected` state. This excludes `Cancelled` (its own peer state), so the terminal-state predicates partition cleanly: a promise is at most one of `async/resolved?` / `async/rejected?` / `async/cancelled?`. Errors if the argument is not a promise.",
"params": [
{
"name": "promise",
"type": "promise"
}
],
"returns": "bool",
"examples": [
"(async/rejected? (async/rejected \"boom\")) ; => #t"
],
"body": "`#t` exactly when `promise` is in the `Rejected` state. This excludes `Cancelled` (its own peer state), so the terminal-state predicates partition cleanly: a promise is at most one of `async/resolved?` / `async/rejected?` / `async/cancelled?`. Errors if the argument is not a promise.\n\n```sema\n(async/rejected? (async/rejected \"boom\")) ; => #t\n```"
},
{
"name": "async/resolved",
"module": "concurrency",
"section": "Promises",
"summary": "Create an already-resolved promise wrapping `value`.",
"examples": [
"(async/resolved value) → async-promise"
],
"body": "```sema\n(async/resolved value) → async-promise\n```\n\nCreate an already-resolved promise wrapping `value`."
},
{
"name": "async/resolved?",
"module": "concurrency",
"section": "Promises",
"summary": "`#t` if `promise` has settled into the `Resolved` state (it completed with a value), `#f` if it is still pending, rejected, or cancelled. Errors if the argument is not a promise.",
"params": [
{
"name": "promise",
"type": "promise"
}
],
"returns": "bool",
"examples": [
"(async/resolved? (async/resolved 42)) ; => #t"
],
"body": "`#t` if `promise` has settled into the `Resolved` state (it completed with a value), `#f` if it is still pending, rejected, or cancelled. Errors if the argument is not a promise.\n\n```sema\n(async/resolved? (async/resolved 42)) ; => #t\n```"
},
{
"name": "async/run",
"module": "concurrency",
"section": "Promises",
"summary": "Run all pending async tasks to completion.",
"examples": [
"(async/run)"
],
"body": "```sema\n(async/run)\n```\n\nRun all pending async tasks to completion."
},
{
"name": "async/sleep",
"module": "concurrency",
"section": "Promises",
"summary": "Inside an async task, yield for `ms` milliseconds on the scheduler's virtual clock. The clock only advances when every task is blocked, jumping to the nearest deadline, so a shorter sleep always wakes before a longer one, deterministically. The scheduler then waits the real time when it advances: on native via `thread::sleep`, and in the browser playground by running eval on a Web Worker that blocks on `Atomics.wait` (so a sleep really pauses while the page stays responsive). Browsers without cross-origin isolation fall back to advancing the clock instantly (ordering preserved). Outside async, calls `thread::sleep` on native. Durations are capped at `86_400_000` ms (1 day).",
"examples": [
"(async/sleep ms)"
],
"body": "```sema\n(async/sleep ms)\n```\n\nInside an async task, yield for `ms` milliseconds on the scheduler's virtual clock. The clock only advances when every task is blocked, jumping to the nearest deadline, so a shorter sleep always wakes before a longer one, deterministically. The scheduler then waits the real time when it advances: on native via `thread::sleep`, and in the browser playground by running eval on a Web Worker that blocks on `Atomics.wait` (so a sleep really pauses while the page stays responsive). Browsers without cross-origin isolation fall back to advancing the clock instantly (ordering preserved). Outside async, calls `thread::sleep` on native. Durations are capped at `86_400_000` ms (1 day)."
},
{
"name": "async/spawn",
"module": "concurrency",
"section": "Promises",
"summary": "Spawn a zero-argument function as an async task. Returns a promise that resolves when the task completes.",
"examples": [
"(async/spawn thunk) → async-promise",
"(define p (async/spawn (fn () (+ 1 2))))\n(async/await p) ; => 3",
"(define p (async (+ 1 2)))\n(await p) ; => 3"
],
"body": "```sema\n(async/spawn thunk) → async-promise\n```\n\nSpawn a zero-argument function as an async task. Returns a promise that resolves when the task completes.\n\n```sema\n(define p (async/spawn (fn () (+ 1 2))))\n(async/await p) ; => 3\n```\n\nUsually called via the `async` special form:\n\n```sema\n(define p (async (+ 1 2)))\n(await p) ; => 3\n```"
},
{
"name": "async/timeout",
"module": "concurrency",
"section": "Promises",
"summary": "Wait for `promise` to resolve, but raise an error if it takes longer than `ms` milliseconds. The underlying task is **not** automatically cancelled; pair with `async/cancel` if you need to free its resources.",
"examples": [
"(async/timeout ms promise) → value",
"(async/timeout 100 (async (do-slow-work)))\n;; raises: async/timeout: operation timed out"
],
"body": "```sema\n(async/timeout ms promise) → value\n```\n\nWait for `promise` to resolve, but raise an error if it takes longer than `ms` milliseconds. The underlying task is **not** automatically cancelled; pair with `async/cancel` if you need to free its resources.\n\n```sema\n(async/timeout 100 (async (do-slow-work)))\n;; raises: async/timeout: operation timed out\n```\n\nA `ms = 0` (or very short) timeout still lets synchronously-ready work finish — it only fires once the virtual clock reaches the deadline with the task still pending. Durations are capped at `86_400_000` ms (1 day)."
},
{
"name": "channel/close",
"module": "concurrency",
"section": "Channels",
"summary": "Close the channel. Subsequent sends will error. Blocked receivers will wake with `nil`.",
"examples": [
"(channel/close ch)"
],
"body": "```sema\n(channel/close ch)\n```\n\nClose the channel. Subsequent sends will error. Blocked receivers will wake with `nil`."
},
{
"name": "channel/closed?",
"module": "concurrency",
"section": "Channels",
"summary": "`#t` if the channel has been closed with `channel/close`, `#f` otherwise. Errors if the argument is not a channel.",
"params": [
{
"name": "ch",
"type": "channel"
}
],
"returns": "bool",
"examples": [
"(define ch (channel/new 1))\n(channel/close ch)\n(channel/closed? ch) ; => #t"
],
"body": "`#t` if the channel has been closed with `channel/close`, `#f` otherwise. Errors if the argument is not a channel.\n\n```sema\n(define ch (channel/new 1))\n(channel/close ch)\n(channel/closed? ch) ; => #t\n```"
},
{
"name": "channel/count",
"module": "concurrency",
"section": "Channels",
"summary": "Return the number of items currently buffered in the channel. Errors if the argument is not a channel.",
"params": [
{
"name": "ch",
"type": "channel"
}
],
"returns": "int",
"examples": [
"(define ch (channel/new 4))\n(channel/send ch 1)\n(channel/send ch 2)\n(channel/count ch) ; => 2"
],
"body": "Return the number of items currently buffered in the channel. Errors if the argument is not a channel.\n\n```sema\n(define ch (channel/new 4))\n(channel/send ch 1)\n(channel/send ch 2)\n(channel/count ch) ; => 2\n```"
},
{
"name": "channel/empty?",
"module": "concurrency",
"section": "Channels",
"summary": "`#t` if the channel's buffer currently holds no items, `#f` otherwise. Errors if the argument is not a channel.",
"params": [
{
"name": "ch",
"type": "channel"
}
],
"returns": "bool",
"examples": [
"(channel/empty? (channel/new 1)) ; => #t"
],
"body": "`#t` if the channel's buffer currently holds no items, `#f` otherwise. Errors if the argument is not a channel.\n\n```sema\n(channel/empty? (channel/new 1)) ; => #t\n```"
},
{
"name": "channel/full?",
"module": "concurrency",
"section": "Channels",
"summary": "`#t` if the channel's buffer has reached its capacity, `#f` otherwise. A further `channel/send` on a full channel yields (inside an async task) or errors (at top level). Errors if the argument is not a channel.",
"params": [
{
"name": "ch",
"type": "channel"
}
],
"returns": "bool",
"examples": [
"(define ch (channel/new 1))\n(channel/send ch 1)\n(channel/full? ch) ; => #t"
],
"body": "`#t` if the channel's buffer has reached its capacity, `#f` otherwise. A further `channel/send` on a full channel yields (inside an async task) or errors (at top level). Errors if the argument is not a channel.\n\n```sema\n(define ch (channel/new 1))\n(channel/send ch 1)\n(channel/full? ch) ; => #t\n```"
},
{
"name": "channel/new",
"module": "concurrency",
"section": "Channels",
"summary": "Create a bounded channel. Default capacity is 1. Capacity must be at least 1.",
"examples": [
"(channel/new) → channel ; capacity 1\n(channel/new capacity) → channel"
],
"body": "```sema\n(channel/new) → channel ; capacity 1\n(channel/new capacity) → channel\n```\n\nCreate a bounded channel. Default capacity is 1. Capacity must be at least 1."
},
{
"name": "channel/recv",
"module": "concurrency",
"section": "Channels",
"summary": "Receive a value from the channel. If the channel is empty and inside an async task, yields until data is available. Outside async context, raises an error if empty. Returns `nil` if the channel is closed and empty.",
"examples": [
"(channel/recv ch) → value"
],
"body": "```sema\n(channel/recv ch) → value\n```\n\nReceive a value from the channel. If the channel is empty and inside an async task, yields until data is available. Outside async context, raises an error if empty. Returns `nil` if the channel is closed and empty."
},
{
"name": "channel/send",
"module": "concurrency",
"section": "Channels",
"summary": "Send a value to the channel. If the channel is full and inside an async task, yields until space is available. Outside async context, raises an error if full. Raises an error if the channel is closed.",
"examples": [
"(channel/send ch value)"
],
"body": "```sema\n(channel/send ch value)\n```\n\nSend a value to the channel. If the channel is full and inside an async task, yields until space is available. Outside async context, raises an error if full. Raises an error if the channel is closed."
},
{
"name": "channel/try-recv",
"module": "concurrency",
"section": "Channels",
"summary": "Non-blocking receive. Returns the next value or `nil` if the channel is empty.",
"examples": [
"(channel/try-recv ch) → value | nil"
],
"body": "```sema\n(channel/try-recv ch) → value | nil\n```\n\nNon-blocking receive. Returns the next value or `nil` if the channel is empty."
},
{
"name": "channel?",
"module": "concurrency",
"section": "Channels",
"summary": "`#t` if `value` is a channel (e.g. one created by `channel/new`), `#f` otherwise.",
"params": [
{
"name": "value"
}
],
"returns": "bool",
"examples": [
"(channel? (channel/new 1)) ; => #t\n(channel? 42) ; => #f"
],
"body": "`#t` if `value` is a channel (e.g. one created by `channel/new`), `#f` otherwise.\n\n```sema\n(channel? (channel/new 1)) ; => #t\n(channel? 42) ; => #f\n```"
},
{
"name": "context/all",
"module": "context",
"section": "Core Functions",
"summary": "Get all context as a merged map.",
"examples": [
"(context/set :a 1)\n(context/set :b 2)\n(context/all) ; => {:a 1 :b 2}"
],
"body": "Get all context as a merged map.\n\n```sema\n(context/set :a 1)\n(context/set :b 2)\n(context/all) ; => {:a 1 :b 2}\n```"
},
{
"name": "context/clear",
"module": "context",
"section": "Core Functions",
"summary": "Clear all context, resetting to an empty state.",
"examples": [
"(context/clear)\n(context/all) ; => {}"
],
"body": "Clear all context, resetting to an empty state.\n\n```sema\n(context/clear)\n(context/all) ; => {}\n```"
},
{
"name": "context/get",
"module": "context",
"section": "Core Functions",
"summary": "Retrieve a value by key. Returns `nil` if the key doesn't exist.",
"examples": [
"(context/get :trace-id) ; => \"abc-123\"\n(context/get :missing) ; => nil"
],
"body": "Retrieve a value by key. Returns `nil` if the key doesn't exist.\n\n```sema\n(context/get :trace-id) ; => \"abc-123\"\n(context/get :missing) ; => nil\n```"
},
{
"name": "context/get-hidden",
"module": "context",
"section": "Hidden Context",
"summary": "Look up a value previously stored with `context/set-hidden`. Returns nil if the key is absent. Hidden values are invisible to the regular `context/get`.",
"params": [
{
"name": "key"
}
],
"examples": [
"(context/get-hidden :api-key) ; => \"sk-secret-123\"\n(context/get :api-key) ; => nil (not visible in regular context)"
],
"body": "Look up a value previously stored with `context/set-hidden`. Returns nil if the key is absent.\nHidden values are invisible to the regular `context/get`.\n\n```sema\n(context/get-hidden :api-key) ; => \"sk-secret-123\"\n(context/get :api-key) ; => nil (not visible in regular context)\n```"
},
{
"name": "context/has-hidden?",
"module": "context",
"section": "Hidden Context",
"summary": "Return `#t` if a hidden context entry exists for `key`, else `#f`.",
"params": [
{
"name": "key"
}
],
"returns": "bool",
"examples": [
"(context/has-hidden? :api-key) ; => #t"
],
"body": "Return `#t` if a hidden context entry exists for `key`, else `#f`.\n\n```sema\n(context/has-hidden? :api-key) ; => #t\n```"
},
{
"name": "context/has?",
"module": "context",
"section": "Core Functions",
"summary": "Check if a key exists in the context.",
"examples": [
"(context/has? :trace-id) ; => #t\n(context/has? :missing) ; => #f"
],
"body": "Check if a key exists in the context.\n\n```sema\n(context/has? :trace-id) ; => #t\n(context/has? :missing) ; => #f\n```"
},
{
"name": "context/merge",
"module": "context",
"section": "Core Functions",
"summary": "Merge a map of key-value pairs into the current context.",
"examples": [
"(context/merge {:trace-id \"abc\" :env \"production\" :version \"1.0\"})\n(context/get :env) ; => \"production\""
],
"body": "Merge a map of key-value pairs into the current context.\n\n```sema\n(context/merge {:trace-id \"abc\" :env \"production\" :version \"1.0\"})\n(context/get :env) ; => \"production\"\n```"
},
{
"name": "context/pop",
"module": "context",
"section": "Stacks",
"summary": "Remove and return the last value from a stack. Returns `nil` if the stack is empty.",
"examples": [
"(context/pop :breadcrumbs) ; => \"settings\"\n(context/stack :breadcrumbs)\n; => (\"login\" \"dashboard\")"
],
"body": "Remove and return the last value from a stack. Returns `nil` if the stack is empty.\n\n```sema\n(context/pop :breadcrumbs) ; => \"settings\"\n(context/stack :breadcrumbs)\n; => (\"login\" \"dashboard\")\n```"
},
{
"name": "context/pull",
"module": "context",
"section": "Core Functions",
"summary": "Get a value and remove it in one step (identical to `context/remove`).",
"examples": [
"(context/set :token \"abc\")\n(context/pull :token) ; => \"abc\"\n(context/has? :token) ; => #f"
],
"body": "Get a value and remove it in one step (identical to `context/remove`).\n\n```sema\n(context/set :token \"abc\")\n(context/pull :token) ; => \"abc\"\n(context/has? :token) ; => #f\n```"
},
{
"name": "context/push",
"module": "context",
"section": "Stacks",
"summary": "Append a value to a named stack.",
"examples": [
"(context/push :breadcrumbs \"login\")\n(context/push :breadcrumbs \"dashboard\")\n(context/push :breadcrumbs \"settings\")"
],
"body": "Append a value to a named stack.\n\n```sema\n(context/push :breadcrumbs \"login\")\n(context/push :breadcrumbs \"dashboard\")\n(context/push :breadcrumbs \"settings\")\n```"
},
{
"name": "context/remove",
"module": "context",
"section": "Core Functions",
"summary": "Remove a key from all context frames. Returns the removed value, or `nil`.",
"examples": [
"(context/set :temp \"data\")\n(context/remove :temp) ; => \"data\"\n(context/remove :temp) ; => nil (already gone)"
],
"body": "Remove a key from all context frames. Returns the removed value, or `nil`.\n\n```sema\n(context/set :temp \"data\")\n(context/remove :temp) ; => \"data\"\n(context/remove :temp) ; => nil (already gone)\n```"
},
{
"name": "context/set",
"module": "context",
"section": "Core Functions",
"summary": "Set a key-value pair in the current context frame.",
"examples": [
"(context/set :trace-id \"abc-123\")\n(context/set :user-id 42)"
],
"body": "Set a key-value pair in the current context frame.\n\n```sema\n(context/set :trace-id \"abc-123\")\n(context/set :user-id 42)\n```"
},
{
"name": "context/set-hidden",
"module": "context",
"section": "Hidden Context",
"summary": "Store a value in the hidden context under `key`. Hidden entries are not returned by `context/all` or `context/get`, making them useful for secrets or internal scoped state.",
"params": [
{
"name": "key"
},
{
"name": "value"
}
],
"returns": "nil",
"examples": [
"(context/set-hidden :api-key \"sk-secret-123\")"
],
"body": "Store a value in the hidden context under `key`. Hidden entries are not returned by `context/all`\nor `context/get`, making them useful for secrets or internal scoped state.\n\n```sema\n(context/set-hidden :api-key \"sk-secret-123\")\n```"
},
{
"name": "context/stack",
"module": "context",
"section": "Stacks",
"summary": "Get all values in a named stack as a list.",
"examples": [
"(context/stack :breadcrumbs)\n; => (\"login\" \"dashboard\" \"settings\")"
],
"body": "Get all values in a named stack as a list.\n\n```sema\n(context/stack :breadcrumbs)\n; => (\"login\" \"dashboard\" \"settings\")\n```"
},
{
"name": "context/with",
"module": "context",
"section": "Scoped Overrides",
"summary": "Push a temporary context frame for the duration of a thunk. The frame is automatically popped when the thunk completes — even if it raises an error.",
"examples": [
"(context/set :env \"production\")\n\n(context/with {:env \"staging\" :debug #t}\n (lambda ()\n (context/get :env) ; => \"staging\"\n (context/get :debug))) ; => #t\n\n(context/get :env) ; => \"production\" (restored)\n(context/get :debug) ; => nil (gone)",
"(context/set :a 1)\n(context/with {:b 2}\n (lambda ()\n (context/with {:c 3}\n (lambda ()\n (list (context/get :a) (context/get :b) (context/get :c))))))\n; => (1 2 3)"
],
"body": "Push a temporary context frame for the duration of a thunk. The frame is automatically popped when the thunk completes — even if it raises an error.\n\n```sema\n(context/set :env \"production\")\n\n(context/with {:env \"staging\" :debug #t}\n (lambda ()\n (context/get :env) ; => \"staging\"\n (context/get :debug))) ; => #t\n\n(context/get :env) ; => \"production\" (restored)\n(context/get :debug) ; => nil (gone)\n```\n\nScopes nest naturally — inner values shadow outer ones:\n\n```sema\n(context/set :a 1)\n(context/with {:b 2}\n (lambda ()\n (context/with {:c 3}\n (lambda ()\n (list (context/get :a) (context/get :b) (context/get :c))))))\n; => (1 2 3)\n```\n\nValues set with `context/set` inside a `context/with` block are written to the inner frame and discarded when the scope exits. If you need a value to persist, set it before entering `context/with`."
},
{
"name": "conversation/add-message",
"module": "conversation",
"summary": "Append a message with the given role to the conversation without calling the model, returning a new conversation. Role must be `:system`, `:user`, `:assistant`, or `:tool`.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "role",
"type": "keyword"
},
{
"name": "content",
"type": "string"
}
],
"returns": "conversation",
"examples": [
"(conversation/add-message conv :assistant \"Sure, I can help with that.\")"
],
"body": "Append a message with the given role to the conversation without calling the model, returning a new conversation. Role must be `:system`, `:user`, `:assistant`, or `:tool`.\n\n```sema\n(conversation/add-message conv :assistant \"Sure, I can help with that.\")\n```"
},
{
"name": "conversation/cost",
"module": "conversation",
"summary": "Estimate the cost in USD of the conversation, treating its estimated tokens (~4 chars per token) as input tokens for the conversation's model. Returns nil if pricing for the model is unknown.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "float",
"examples": [
"(conversation/cost conv) ; => 0.0014"
],
"body": "Estimate the cost in USD of the conversation, treating its estimated tokens (~4 chars per token) as input tokens for the conversation's model. Returns nil if pricing for the model is unknown.\n\n```sema\n(conversation/cost conv) ; => 0.0014\n```"
},
{
"name": "conversation/filter",
"module": "conversation",
"summary": "Return a new conversation keeping only messages for which `(pred msg)` is truthy. The predicate receives each message value.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "pred"
}
],
"returns": "conversation",
"examples": [
"(conversation/filter conv (fn [m] (= (message/role m) :user)))"
],
"body": "Return a new conversation keeping only messages for which `(pred msg)` is truthy. The predicate receives each message value.\n\n```sema\n(conversation/filter conv (fn [m] (= (message/role m) :user)))\n```"
},
{
"name": "conversation/fork",
"module": "conversation",
"summary": "Return an independent copy of the conversation. Since conversations are immutable, the fork shares no future mutations and can be branched separately.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "conversation",
"examples": [
"(let [branch (conversation/fork conv)]\n (conversation/say branch \"Try a different approach\"))"
],
"body": "Return an independent copy of the conversation. Since conversations are immutable, the fork shares no future mutations and can be branched separately.\n\n```sema\n(let [branch (conversation/fork conv)]\n (conversation/say branch \"Try a different approach\"))\n```"
},
{
"name": "conversation/last-reply",
"module": "conversation",
"summary": "Return the content of the most recent assistant message as a string. Errors if the conversation has no assistant reply.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "string",
"examples": [
"(conversation/last-reply conv)"
],
"body": "Return the content of the most recent assistant message as a string. Errors if the conversation has no assistant reply.\n\n```sema\n(conversation/last-reply conv)\n```"
},
{
"name": "conversation/map",
"module": "conversation",
"summary": "Apply `f` to each message in the conversation and return the list of results. Unlike `conversation/filter`, this returns a plain list rather than a conversation.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "f"
}
],
"returns": "list",
"examples": [
"(conversation/map conv message/content)"
],
"body": "Apply `f` to each message in the conversation and return the list of results. Unlike `conversation/filter`, this returns a plain list rather than a conversation.\n\n```sema\n(conversation/map conv message/content)\n```"
},
{
"name": "conversation/messages",
"module": "conversation",
"summary": "Return the conversation's messages as a list of message values.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "list",
"examples": [
"(conversation/messages conv)"
],
"body": "Return the conversation's messages as a list of message values.\n\n```sema\n(conversation/messages conv)\n```"
},
{
"name": "conversation/model",
"module": "conversation",
"summary": "Return the model name associated with the conversation.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "string",
"examples": [
"(conversation/model conv) ; => \"claude-sonnet-4\""
],
"body": "Return the model name associated with the conversation.\n\n```sema\n(conversation/model conv) ; => \"claude-sonnet-4\"\n```"
},
{
"name": "conversation/new",
"module": "conversation",
"summary": "Create a new, empty conversation. The optional map sets the `:model` and any other keys are stored as string metadata.",
"params": [
{
"name": "opts",
"type": "map"
}
],
"returns": "conversation",
"examples": [
"(conversation/new {:model \"claude-sonnet-4\" :user \"alice\"})"
],
"body": "Create a new, empty conversation. The optional map sets the `:model` and any other keys are stored as string metadata.\n\n```sema\n(conversation/new {:model \"claude-sonnet-4\" :user \"alice\"})\n```"
},
{
"name": "conversation/say",
"module": "conversation",
"summary": "Send a user message to the conversation's model and return a new conversation with the user message and the assistant's reply appended. The optional opts map accepts `:temperature`, `:max-tokens`, and `:system`.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "message",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "conversation",
"examples": [
"(conversation/say conv \"What's the capital of France?\" {:temperature 0.2})"
],
"body": "Send a user message to the conversation's model and return a new conversation with the user message and the assistant's reply appended. The optional opts map accepts `:temperature`, `:max-tokens`, and `:system`.\n\n```sema\n(conversation/say conv \"What's the capital of France?\" {:temperature 0.2})\n```"
},
{
"name": "conversation/say-as",
"module": "conversation",
"summary": "Like `conversation/say` but uses a one-off system prompt (a string or prompt value) for this turn only; the conversation's existing system message is preserved in the returned conversation. The optional opts map accepts `:temperature` and `:max-tokens`.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "system"
},
{
"name": "message",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "conversation",
"examples": [
"(conversation/say-as conv \"You are a terse pirate.\" \"Describe the weather.\")"
],
"body": "Like `conversation/say` but uses a one-off system prompt (a string or prompt value) for this turn only; the conversation's existing system message is preserved in the returned conversation. The optional opts map accepts `:temperature` and `:max-tokens`.\n\n```sema\n(conversation/say-as conv \"You are a terse pirate.\" \"Describe the weather.\")\n```"
},
{
"name": "conversation/set-system",
"module": "conversation",
"summary": "Return a new conversation with its system message set to (or replaced by) the given string, placed first among the messages.",
"params": [
{
"name": "conv",
"type": "conversation"
},
{
"name": "system",
"type": "string"
}
],
"returns": "conversation",
"examples": [
"(conversation/set-system conv \"You are a concise expert.\")"
],
"body": "Return a new conversation with its system message set to (or replaced by) the given string, placed first among the messages.\n\n```sema\n(conversation/set-system conv \"You are a concise expert.\")\n```"
},
{
"name": "conversation/system",
"module": "conversation",
"summary": "Return the content of the conversation's system message, or nil if none is set.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "string",
"examples": [
"(conversation/system conv) ; => \"You are a helpful assistant.\""
],
"body": "Return the content of the conversation's system message, or nil if none is set.\n\n```sema\n(conversation/system conv) ; => \"You are a helpful assistant.\"\n```"
},
{
"name": "conversation/token-count",
"module": "conversation",
"summary": "Estimate the total number of tokens across all message contents in the conversation, using a ~4-characters-per-token heuristic.",
"params": [
{
"name": "conv",
"type": "conversation"
}
],
"returns": "int",
"examples": [
"(conversation/token-count conv) ; => 312"
],
"body": "Estimate the total number of tokens across all message contents in the conversation, using a ~4-characters-per-token heuristic.\n\n```sema\n(conversation/token-count conv) ; => 312\n```"
},
{
"name": "base64/decode",
"module": "crypto",
"section": "Base64 Encoding",
"summary": "Decode a Base64 string back to a UTF-8 string. Errors if the decoded bytes are not valid UTF-8.",
"examples": [
"(base64/decode \"aGVsbG8=\") ; => \"hello\""
],
"body": "Decode a Base64 string back to a UTF-8 string. Errors if the decoded bytes are not valid UTF-8.\n\n**Signature:** `(base64/decode base64-string) → string`\n\n```sema\n(base64/decode \"aGVsbG8=\") ; => \"hello\"\n```"
},
{
"name": "base64/decode-bytes",
"module": "crypto",
"section": "Base64 Encoding",
"summary": "Decode a Base64 string to a bytevector. Unlike `base64/decode`, this does not require valid UTF-8.",
"examples": [
"(base64/decode-bytes \"aGVsbG8=\") ; => #u8(104 101 108 108 111)"
],
"body": "Decode a Base64 string to a bytevector. Unlike `base64/decode`, this does not require valid UTF-8.\n\n**Signature:** `(base64/decode-bytes base64-string) → bytevector`\n\n```sema\n(base64/decode-bytes \"aGVsbG8=\") ; => #u8(104 101 108 108 111)\n```"
},
{
"name": "base64/encode",
"module": "crypto",
"section": "Base64 Encoding",
"summary": "Encode a string to Base64.",
"examples": [
"(base64/encode \"hello\") ; => \"aGVsbG8=\"\n(base64/encode \"\") ; => \"\""
],
"body": "Encode a string to Base64.\n\n**Signature:** `(base64/encode string) → string`\n\n```sema\n(base64/encode \"hello\") ; => \"aGVsbG8=\"\n(base64/encode \"\") ; => \"\"\n```"
},
{
"name": "base64/encode-bytes",
"module": "crypto",
"section": "Base64 Encoding",
"summary": "Encode a bytevector to Base64.",
"examples": [
"(base64/encode-bytes #u8(104 101 108 108 111)) ; => \"aGVsbG8=\""
],
"body": "Encode a bytevector to Base64.\n\n**Signature:** `(base64/encode-bytes bytevector) → string`\n\n```sema\n(base64/encode-bytes #u8(104 101 108 108 111)) ; => \"aGVsbG8=\"\n```"
},
{
"name": "hash/hmac-sha256",
"module": "crypto",
"section": "Hashing",
"summary": "Compute an HMAC-SHA256 message authentication code. Returns a 64-character hex string.",
"examples": [
"(hash/hmac-sha256 \"secret-key\" \"message\")\n; => \"hex-encoded-hmac...\"",
";; Verify a webhook signature from a provider\n(define (verify-webhook payload secret signature)\n (equal? (hash/hmac-sha256 secret payload) signature))"
],
"body": "Compute an HMAC-SHA256 message authentication code. Returns a 64-character hex string.\n\n**Signature:** `(hash/hmac-sha256 key message) → string`\n\n```sema\n(hash/hmac-sha256 \"secret-key\" \"message\")\n; => \"hex-encoded-hmac...\"\n```\n\n**Webhook verification example:**\n\n```sema\n;; Verify a webhook signature from a provider\n(define (verify-webhook payload secret signature)\n (equal? (hash/hmac-sha256 secret payload) signature))\n```"
},
{
"name": "hash/md5",
"module": "crypto",
"section": "Hashing",
"summary": "Compute the MD5 hash of a string. Returns a 32-character hex string.",
"examples": [
"(hash/md5 \"hello\") ; => \"5d41402abc4b2a76b9719d911017c592\""
],
"body": "Compute the MD5 hash of a string. Returns a 32-character hex string.\n\n**Signature:** `(hash/md5 string) → string`\n\n```sema\n(hash/md5 \"hello\") ; => \"5d41402abc4b2a76b9719d911017c592\"\n```"
},
{
"name": "hash/sha256",
"module": "crypto",
"section": "Hashing",
"summary": "Compute the SHA-256 hash of a string. Returns a 64-character hex string.",
"examples": [
"(hash/sha256 \"hello\")\n; => \"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824\""
],
"body": "Compute the SHA-256 hash of a string. Returns a 64-character hex string.\n\n**Signature:** `(hash/sha256 string) → string`\n\n```sema\n(hash/sha256 \"hello\")\n; => \"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824\"\n```"
},
{
"name": "uuid/v4",
"module": "crypto",
"section": "UUID",
"summary": "Generate a random UUID v4 string.",
"examples": [
"(uuid/v4) ; => \"550e8400-e29b-41d4-a716-446655440000\" (varies)",
"(equal? (uuid/v4) (uuid/v4)) ; => #f"
],
"body": "Generate a random UUID v4 string.\n\n**Signature:** `(uuid/v4) → string`\n\n```sema\n(uuid/v4) ; => \"550e8400-e29b-41d4-a716-446655440000\" (varies)\n```\n\nEach call returns a new unique identifier:\n\n```sema\n(equal? (uuid/v4) (uuid/v4)) ; => #f\n```"
},
{
"name": "csv/encode",
"module": "csv",
"section": "CSV",
"summary": "Encode a list of lists (or vectors) into a CSV string. Each inner list/vector becomes one row. Non-string values are stringified automatically.",
"examples": [
"(csv/encode '((\"a\" \"b\") (\"1\" \"2\")))\n; => \"a,b\\n1,2\\n\"",
"(csv/encode '((\"name\" \"score\") (\"Ada\" 100)))\n; => \"name,score\\nAda,100\\n\""
],
"body": "Encode a list of lists (or vectors) into a CSV string. Each inner list/vector becomes one row. Non-string values are stringified automatically.\n\n**Signature:** `(csv/encode rows) → string`\n\n```sema\n(csv/encode '((\"a\" \"b\") (\"1\" \"2\")))\n; => \"a,b\\n1,2\\n\"\n```\n\nNumeric and other values are converted to strings:\n\n```sema\n(csv/encode '((\"name\" \"score\") (\"Ada\" 100)))\n; => \"name,score\\nAda,100\\n\"\n```"
},
{
"name": "csv/parse",
"module": "csv",
"section": "CSV",
"summary": "Parse a CSV string into a list of lists (rows of fields). No header processing — every row is returned as-is.",
"examples": [
"(csv/parse \"a,b\\n1,2\\n3,4\")\n; => ((\"a\" \"b\") (\"1\" \"2\") (\"3\" \"4\"))",
"(csv/parse \"name,bio\\n\\\"Ada\\\",\\\"Mathematician, writer\\\"\\n\")\n; => ((\"name\" \"bio\") (\"Ada\" \"Mathematician, writer\"))"
],
"body": "Parse a CSV string into a list of lists (rows of fields). No header processing — every row is returned as-is.\n\n**Signature:** `(csv/parse csv-string) → list`\n\n```sema\n(csv/parse \"a,b\\n1,2\\n3,4\")\n; => ((\"a\" \"b\") (\"1\" \"2\") (\"3\" \"4\"))\n```\n\nQuoted fields with commas and newlines are handled correctly:\n\n```sema\n(csv/parse \"name,bio\\n\\\"Ada\\\",\\\"Mathematician, writer\\\"\\n\")\n; => ((\"name\" \"bio\") (\"Ada\" \"Mathematician, writer\"))\n```"
},
{
"name": "csv/parse-maps",
"module": "csv",
"section": "CSV",
"summary": "Parse a CSV string into a list of maps. The first row is used as headers, which become keyword keys in each map.",
"examples": [
"(csv/parse-maps \"name,age\\nAda,36\\nBob,25\")\n; => ({:age \"36\" :name \"Ada\"} {:age \"25\" :name \"Bob\"})",
"(define rows (csv/parse-maps \"name,age\\nAda,36\\nBob,25\"))\n(:name (first rows)) ; => \"Ada\""
],
"body": "Parse a CSV string into a list of maps. The first row is used as headers, which become keyword keys in each map.\n\n**Signature:** `(csv/parse-maps csv-string) → list`\n\n```sema\n(csv/parse-maps \"name,age\\nAda,36\\nBob,25\")\n; => ({:age \"36\" :name \"Ada\"} {:age \"25\" :name \"Bob\"})\n```\n\nAccess fields by keyword:\n\n```sema\n(define rows (csv/parse-maps \"name,age\\nAda,36\\nBob,25\"))\n(:name (first rows)) ; => \"Ada\"\n```"
},
{
"name": "sleep",
"module": "datetime",
"section": "Delay",
"summary": "Pause execution for a given number of milliseconds. Returns `nil`.",
"examples": [
"(sleep milliseconds) ; => nil",
"(sleep 1000) ; sleep for 1 second\n(sleep 500) ; sleep for 500ms\n(sleep 0) ; yield (no-op pause)"
],
"body": "Pause execution for a given number of milliseconds. Returns `nil`.\n\n```sema\n(sleep milliseconds) ; => nil\n```\n\n```sema\n(sleep 1000) ; sleep for 1 second\n(sleep 500) ; sleep for 500ms\n(sleep 0) ; yield (no-op pause)\n```\n\nNote that `sleep` takes **milliseconds** (not seconds), unlike the `time/` functions which work in seconds."
},
{
"name": "time-ms",
"aliases": [
"time/now-ms"
],
"module": "datetime",
"section": "Current Time",
"summary": "Return the current time as Unix milliseconds (integer). Defined in the system module but useful alongside datetime operations.",
"examples": [
"(time-ms) ; => 1707955200123"
],
"body": "Return the current time as Unix milliseconds (integer). Defined in the system module but useful alongside datetime operations.\n\n```sema\n(time-ms) ; => 1707955200123\n```"
},
{
"name": "time/add",
"module": "datetime",
"section": "Arithmetic",
"summary": "Add seconds to a timestamp. Returns a new timestamp. Use negative values to subtract.",
"examples": [
"(time/add timestamp seconds) ; => float (timestamp)",
"(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/add ts 3600) ; one hour later => 1736946600.0\n(time/add ts 86400) ; one day later => 1737029400.0\n(time/add ts -3600) ; one hour earlier => 1736939400.0\n(time/add ts (* 7 86400)) ; one week later"
],
"body": "Add seconds to a timestamp. Returns a new timestamp. Use negative values to subtract.\n\n```sema\n(time/add timestamp seconds) ; => float (timestamp)\n```\n\n```sema\n(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/add ts 3600) ; one hour later => 1736946600.0\n(time/add ts 86400) ; one day later => 1737029400.0\n(time/add ts -3600) ; one hour earlier => 1736939400.0\n(time/add ts (* 7 86400)) ; one week later\n```\n\nCommon durations in seconds:\n\n| Duration | Seconds |\n|----------|---------|\n| 1 minute | `60` |\n| 1 hour | `3600` |\n| 1 day | `86400` |\n| 1 week | `604800` |\n| 30 days | `2592000` |"
},
{
"name": "time/date-parts",
"module": "datetime",
"section": "Date Decomposition",
"summary": "Decompose a UTC Unix timestamp into a map of date/time components.",
"examples": [
"(time/date-parts timestamp) ; => map",
"(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n(define parts (time/date-parts ts))\n\n(get parts :year) ; => 2025\n(get parts :month) ; => 1\n(get parts :day) ; => 15\n(get parts :hour) ; => 12\n(get parts :minute) ; => 10\n(get parts :second) ; => 0\n(get parts :weekday) ; => \"Wednesday\""
],
"body": "Decompose a UTC Unix timestamp into a map of date/time components.\n\n```sema\n(time/date-parts timestamp) ; => map\n```\n\n```sema\n(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n(define parts (time/date-parts ts))\n\n(get parts :year) ; => 2025\n(get parts :month) ; => 1\n(get parts :day) ; => 15\n(get parts :hour) ; => 12\n(get parts :minute) ; => 10\n(get parts :second) ; => 0\n(get parts :weekday) ; => \"Wednesday\"\n```\n\nThe returned map contains these keys:\n\n| Key | Type | Description | Example |\n|-----|------|-------------|---------|\n| `:year` | integer | Four-digit year | `2025` |\n| `:month` | integer | Month (1–12) | `1` |\n| `:day` | integer | Day of month (1–31) | `15` |\n| `:hour` | integer | Hour (0–23) | `12` |\n| `:minute` | integer | Minute (0–59) | `10` |\n| `:second` | integer | Second (0–59) | `0` |\n| `:weekday` | string | Full weekday name | `\"Wednesday\"` |\n\nThe `:weekday` value is the full English weekday name: `\"Monday\"`, `\"Tuesday\"`, `\"Wednesday\"`, `\"Thursday\"`, `\"Friday\"`, `\"Saturday\"`, `\"Sunday\"`."
},
{
"name": "time/diff",
"module": "datetime",
"section": "Arithmetic",
"summary": "Compute the difference between two timestamps in seconds. Returns `t1 - t2` (the first argument minus the second). The result can be negative.",
"examples": [
"(time/diff t1 t2) ; => float (seconds)",
"(define morning 1736935800.0) ; 2025-01-15 10:10:00 UTC\n(define afternoon 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/diff afternoon morning) ; => 7200.0 (2 hours)\n(time/diff morning afternoon) ; => -7200.0 (negative — morning is earlier)\n(time/diff morning morning) ; => 0.0"
],
"body": "Compute the difference between two timestamps in seconds. Returns `t1 - t2` (the first argument minus the second). The result can be negative.\n\n```sema\n(time/diff t1 t2) ; => float (seconds)\n```\n\n```sema\n(define morning 1736935800.0) ; 2025-01-15 10:10:00 UTC\n(define afternoon 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/diff afternoon morning) ; => 7200.0 (2 hours)\n(time/diff morning afternoon) ; => -7200.0 (negative — morning is earlier)\n(time/diff morning morning) ; => 0.0\n```\n\n`time/diff` returns a signed value: positive when `t1 > t2`, negative when `t1 < t2`. Use `abs` if you need the absolute elapsed time regardless of order."
},
{
"name": "time/format",
"module": "datetime",
"section": "Formatting",
"summary": "Format a UTC Unix timestamp using a [strftime](#strftime-format-directives)-style format string.",
"examples": [
"(time/format timestamp format-string) ; => string",
"(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/format ts \"%Y-%m-%d\") ; => \"2025-01-15\"\n(time/format ts \"%H:%M:%S\") ; => \"12:10:00\"\n(time/format ts \"%Y-%m-%d %H:%M:%S\") ; => \"2025-01-15 12:10:00\"\n(time/format ts \"%A, %B %d, %Y\") ; => \"Wednesday, January 15, 2025\"\n(time/format ts \"%F\") ; => \"2025-01-15\" (shorthand for %Y-%m-%d)\n(time/format ts \"%T\") ; => \"12:10:00\" (shorthand for %H:%M:%S)"
],
"body": "Format a UTC Unix timestamp using a [strftime](#strftime-format-directives)-style format string.\n\n```sema\n(time/format timestamp format-string) ; => string\n```\n\n```sema\n(define ts 1736943000.0) ; 2025-01-15 12:10:00 UTC\n\n(time/format ts \"%Y-%m-%d\") ; => \"2025-01-15\"\n(time/format ts \"%H:%M:%S\") ; => \"12:10:00\"\n(time/format ts \"%Y-%m-%d %H:%M:%S\") ; => \"2025-01-15 12:10:00\"\n(time/format ts \"%A, %B %d, %Y\") ; => \"Wednesday, January 15, 2025\"\n(time/format ts \"%F\") ; => \"2025-01-15\" (shorthand for %Y-%m-%d)\n(time/format ts \"%T\") ; => \"12:10:00\" (shorthand for %H:%M:%S)\n```"
},
{
"name": "time/now",
"module": "datetime",
"section": "Current Time",
"summary": "Return the current time as a UTC Unix timestamp in seconds, with fractional milliseconds.",
"examples": [
"(time/now) ; => 1707955200.123",
"(define now (time/now))\n(println \"Current timestamp: \" now)\n\n;; Extract just the seconds (truncate fractional part)\n(define whole-seconds (floor now))"
],
"body": "Return the current time as a UTC Unix timestamp in seconds, with fractional milliseconds.\n\n```sema\n(time/now) ; => 1707955200.123\n```\n\nThe integer part is seconds since the Unix epoch; the fractional part provides millisecond precision.\n\n```sema\n(define now (time/now))\n(println \"Current timestamp: \" now)\n\n;; Extract just the seconds (truncate fractional part)\n(define whole-seconds (floor now))\n```"
},
{
"name": "time/parse",
"module": "datetime",
"section": "Parsing",
"summary": "Parse a date string into a UTC Unix timestamp using a [strftime](#strftime-format-directives)-style format string. The input is treated as a **UTC naive datetime** — no timezone information is expected or applied.",
"examples": [
"(time/parse date-string format-string) ; => float (UTC timestamp)",
"(time/parse \"2025-01-15 12:10:00\" \"%Y-%m-%d %H:%M:%S\") ; => 1736943000.0\n(time/parse \"2025-01-15 00:00:00\" \"%Y-%m-%d %H:%M:%S\") ; => 1736899200.0\n(time/parse \"15/01/2025 14:30:00\" \"%d/%m/%Y %H:%M:%S\") ; => 1736951400.0",
"(define ts 1700000000.0)\n(define formatted (time/format ts \"%Y-%m-%d %H:%M:%S\"))\n(define parsed (time/parse formatted \"%Y-%m-%d %H:%M:%S\"))\n(= parsed ts) ; => #t"
],
"body": "Parse a date string into a UTC Unix timestamp using a [strftime](#strftime-format-directives)-style format string. The input is treated as a **UTC naive datetime** — no timezone information is expected or applied.\n\n```sema\n(time/parse date-string format-string) ; => float (UTC timestamp)\n```\n\n```sema\n(time/parse \"2025-01-15 12:10:00\" \"%Y-%m-%d %H:%M:%S\") ; => 1736943000.0\n(time/parse \"2025-01-15 00:00:00\" \"%Y-%m-%d %H:%M:%S\") ; => 1736899200.0\n(time/parse \"15/01/2025 14:30:00\" \"%d/%m/%Y %H:%M:%S\") ; => 1736951400.0\n```\n\nThe format string must provide enough directives to fully specify a date and time. Parsing a date-only string like `\"%Y-%m-%d\"` without time components will fail — always include time directives (e.g., `%H:%M:%S`).\n\nThe wall-clock time in the string is **always interpreted as UTC**, regardless of any offset present. `time/parse` does not apply timezone offsets. To work with another timezone, convert the value to UTC yourself (subtract the offset) before parsing, then format/compute in UTC.\n\n**Roundtrip** — formatting a timestamp and parsing it back yields the original value:\n\n```sema\n(define ts 1700000000.0)\n(define formatted (time/format ts \"%Y-%m-%d %H:%M:%S\"))\n(define parsed (time/parse formatted \"%Y-%m-%d %H:%M:%S\"))\n(= parsed ts) ; => #t\n```\n\n`time/parse` returns whole seconds — sub-second precision from the original timestamp is lost when roundtripping through format/parse."
},
{
"name": "embedding/->list",
"module": "embedding",
"summary": "Convert an embedding bytevector into a list of float values, one per dimension.",
"params": [
{
"name": "embedding",
"type": "bytevector"
}
],
"returns": "list",
"examples": [
"(embedding/->list (llm/embed \"hello\")) ; => (0.0123 -0.045 ...)"
],
"body": "Convert an embedding bytevector into a list of float values, one per dimension.\n\n```sema\n(embedding/->list (llm/embed \"hello\")) ; => (0.0123 -0.045 ...)\n```"
},
{
"name": "embedding/length",
"module": "embedding",
"summary": "Return the number of dimensions in an embedding. Embeddings are bytevectors of little-endian f64 values, so the length is the byte length divided by 8.",
"params": [
{
"name": "embedding",
"type": "bytevector"
}
],
"returns": "int",
"examples": [
"(embedding/length (llm/embed \"hello\")) ; => 1536"
],
"body": "Return the number of dimensions in an embedding. Embeddings are bytevectors of little-endian f64 values, so the length is the byte length divided by 8.\n\n```sema\n(embedding/length (llm/embed \"hello\")) ; => 1536\n```"
},
{
"name": "embedding/list->embedding",
"module": "embedding",
"summary": "Convert a list or vector of numbers into an embedding bytevector (little-endian f64 per element). Inverse of `embedding/->list`.",
"params": [
{
"name": "nums"
}
],
"returns": "bytevector",
"examples": [
"(embedding/list->embedding [0.1 0.2 0.3])"
],
"body": "Convert a list or vector of numbers into an embedding bytevector (little-endian f64 per element). Inverse of `embedding/->list`.\n\n```sema\n(embedding/list->embedding [0.1 0.2 0.3])\n```"
},
{
"name": "embedding/ref",
"module": "embedding",
"summary": "Return the embedding value at the given dimension index as a float. Errors if the index is out of bounds.",
"params": [
{
"name": "embedding",
"type": "bytevector"
},
{
"name": "index",
"type": "int"
}
],
"returns": "float",
"examples": [
"(embedding/ref emb 0) ; => 0.0123"
],
"body": "Return the embedding value at the given dimension index as a float. Errors if the index is out of bounds.\n\n```sema\n(embedding/ref emb 0) ; => 0.0123\n```"
},
{
"name": "display",
"module": "file-io",
"section": "Console I/O",
"summary": "Print a value without a trailing newline.",
"examples": [
"(display \"no newline\")\n(display 42)"
],
"body": "Print a value without a trailing newline.\n\n```sema\n(display \"no newline\")\n(display 42)\n```"
},
{
"name": "file/append",
"module": "file-io",
"section": "File Operations",
"summary": "Append a string to a file.",
"examples": [
"(file/append \"log.txt\" \"new line\\n\")"
],
"body": "Append a string to a file.\n\n```sema\n(file/append \"log.txt\" \"new line\\n\")\n```"
},
{
"name": "file/copy",
"module": "file-io",
"section": "File Operations",
"summary": "Copy a file.",
"examples": [
"(file/copy \"src.txt\" \"dst.txt\")"
],
"body": "Copy a file.\n\n```sema\n(file/copy \"src.txt\" \"dst.txt\")\n```"
},
{
"name": "file/delete",
"module": "file-io",
"section": "File Operations",
"summary": "Delete a file.",
"examples": [
"(file/delete \"tmp.txt\")"
],
"body": "Delete a file.\n\n```sema\n(file/delete \"tmp.txt\")\n```"
},
{
"name": "file/exists?",
"module": "file-io",
"section": "File Predicates",
"summary": "Test if a file or directory exists.",
"examples": [
"(file/exists? \"data.txt\") ; => #t or #f"
],
"body": "Test if a file or directory exists.\n\n```sema\n(file/exists? \"data.txt\") ; => #t or #f\n```"
},
{
"name": "file/fold-lines",
"module": "file-io",
"section": "File Operations",
"summary": "Fold over lines of a file with an accumulator. Uses a 256KB buffer for high throughput on large files.",
"examples": [
"(file/fold-lines \"data.csv\"\n (fn (acc line) (+ acc 1))\n 0)\n; => number of lines"
],
"body": "Fold over lines of a file with an accumulator. Uses a 256KB buffer for high throughput on large files.\n\n```sema\n(file/fold-lines \"data.csv\"\n (fn (acc line) (+ acc 1))\n 0)\n; => number of lines\n```"
},
{
"name": "file/for-each-line",
"module": "file-io",
"section": "File Operations",
"summary": "Iterate over lines of a file, calling a function on each line. Memory-efficient for large files.",
"examples": [
"(file/for-each-line \"data.txt\"\n (fn (line) (println line)))"
],
"body": "Iterate over lines of a file, calling a function on each line. Memory-efficient for large files.\n\n```sema\n(file/for-each-line \"data.txt\"\n (fn (line) (println line)))\n```"
},
{
"name": "file/glob",
"module": "file-io",
"section": "Directory Operations",
"summary": "Find files matching a glob pattern.",
"examples": [
"(file/glob \"src/**/*.rs\") ; => (\"src/main.rs\" \"src/lib.rs\" ...)\n(file/glob \"*.txt\") ; => (\"readme.txt\" \"notes.txt\")"
],
"body": "Find files matching a glob pattern.\n\n```sema\n(file/glob \"src/**/*.rs\") ; => (\"src/main.rs\" \"src/lib.rs\" ...)\n(file/glob \"*.txt\") ; => (\"readme.txt\" \"notes.txt\")\n```"
},
{
"name": "file/info",
"module": "file-io",
"section": "Directory Operations",
"summary": "Get file metadata. Returns a map with `:size`, `:modified`, and other keys.",
"examples": [
"(file/info \"data.txt\") ; => {:size 1234 :modified 1707955200 ...}"
],
"body": "Get file metadata. Returns a map with `:size`, `:modified`, and other keys.\n\n```sema\n(file/info \"data.txt\") ; => {:size 1234 :modified 1707955200 ...}\n```"
},
{
"name": "file/is-directory?",
"module": "file-io",
"section": "File Predicates",
"summary": "Test if a path is a directory.",
"examples": [
"(file/is-directory? \"src/\") ; => #t"
],
"body": "Test if a path is a directory.\n\n```sema\n(file/is-directory? \"src/\") ; => #t\n```"
},
{
"name": "file/is-file?",
"module": "file-io",
"section": "File Predicates",
"summary": "Test if a path is a regular file.",
"examples": [
"(file/is-file? \"data.txt\") ; => #t"
],
"body": "Test if a path is a regular file.\n\n```sema\n(file/is-file? \"data.txt\") ; => #t\n```"
},
{
"name": "file/is-symlink?",
"module": "file-io",
"section": "File Predicates",
"summary": "Test if a path is a symbolic link.",
"examples": [
"(file/is-symlink? \"link\") ; => #t or #f"
],
"body": "Test if a path is a symbolic link.\n\n```sema\n(file/is-symlink? \"link\") ; => #t or #f\n```"
},
{
"name": "file/list",
"module": "file-io",
"section": "Directory Operations",
"summary": "List entries in a directory.",
"examples": [
"(file/list \"src/\") ; => (\"main.rs\" \"lib.rs\" ...)"
],
"body": "List entries in a directory.\n\n```sema\n(file/list \"src/\") ; => (\"main.rs\" \"lib.rs\" ...)\n```"
},
{
"name": "file/mkdir",
"module": "file-io",
"section": "Directory Operations",
"summary": "Create a directory.",
"examples": [
"(file/mkdir \"new-dir\")"
],
"body": "Create a directory.\n\n```sema\n(file/mkdir \"new-dir\")\n```"
},
{
"name": "file/read",
"module": "file-io",
"section": "File Operations",
"summary": "Read the entire contents of a file as a string.",
"examples": [
"(file/read \"data.txt\") ; => \"file contents...\""
],
"body": "Read the entire contents of a file as a string.\n\n```sema\n(file/read \"data.txt\") ; => \"file contents...\"\n```"
},
{
"name": "file/read-bytes",
"module": "file-io",
"section": "Binary File I/O",
"summary": "Read a file as a bytevector (binary data).",
"examples": [
"(file/read-bytes \"image.png\") ; => #u8(137 80 78 71 ...)"
],
"body": "Read a file as a bytevector (binary data).\n\n```sema\n(file/read-bytes \"image.png\") ; => #u8(137 80 78 71 ...)\n```"
},
{
"name": "file/read-lines",
"module": "file-io",
"section": "File Operations",
"summary": "Read a file as a list of lines. Handles both `\\n` and `\\r\\n` line endings. An empty file returns an empty list.",
"examples": [
"(file/read-lines \"data.txt\") ; => (\"line 1\" \"line 2\" \"line 3\")\n(file/read-lines \"empty.txt\") ; => ()"
],
"body": "Read a file as a list of lines. Handles both `\\n` and `\\r\\n` line endings. An empty file returns an empty list.\n\n```sema\n(file/read-lines \"data.txt\") ; => (\"line 1\" \"line 2\" \"line 3\")\n(file/read-lines \"empty.txt\") ; => ()\n```"
},
{
"name": "file/rename",
"module": "file-io",
"section": "File Operations",
"summary": "Rename or move a file.",
"examples": [
"(file/rename \"old.txt\" \"new.txt\")"
],
"body": "Rename or move a file.\n\n```sema\n(file/rename \"old.txt\" \"new.txt\")\n```"
},
{
"name": "file/write",
"module": "file-io",
"section": "File Operations",
"summary": "Write a string to a file, overwriting any existing content.",
"examples": [
"(file/write \"out.txt\" \"content\")"
],
"body": "Write a string to a file, overwriting any existing content.\n\n```sema\n(file/write \"out.txt\" \"content\")\n```"
},
{
"name": "file/write-bytes",
"module": "file-io",
"section": "Binary File I/O",
"summary": "Write a bytevector to a file.",
"examples": [
"(file/write-bytes \"output.bin\" my-bytes)"
],
"body": "Write a bytevector to a file.\n\n```sema\n(file/write-bytes \"output.bin\" my-bytes)\n```"
},
{
"name": "file/write-lines",
"module": "file-io",
"section": "File Operations",
"summary": "Write a list of strings to a file, one per line.",
"examples": [
"(file/write-lines \"out.txt\" '(\"a\" \"b\" \"c\"))"
],
"body": "Write a list of strings to a file, one per line.\n\n```sema\n(file/write-lines \"out.txt\" '(\"a\" \"b\" \"c\"))\n```"
},
{
"name": "io/eof?",
"module": "file-io",
"section": "Console I/O",
"summary": "Return `#t` after any stdin read (`io/read-line`, `io/read-stdin`, `io/read-key`) has signalled EOF. Non-breaking alternative to checking `io/read-line` for `nil`.",
"examples": [
"(define line (io/read-line))\n(when (io/eof?)\n (println \"stdin closed\"))"
],
"body": "Return `#t` after any stdin read (`io/read-line`, `io/read-stdin`, `io/read-key`) has signalled EOF. Non-breaking alternative to checking `io/read-line` for `nil`.\n\n```sema\n(define line (io/read-line))\n(when (io/eof?)\n (println \"stdin closed\"))\n```"
},
{
"name": "io/flush",
"module": "file-io",
"section": "Console I/O",
"summary": "Flush stdout. Useful when writing a prompt without a trailing newline before reading input.",
"examples": [
"(display \"name> \")\n(io/flush)\n(define name (io/read-line))"
],
"body": "Flush stdout. Useful when writing a prompt without a trailing newline before reading input.\n\n```sema\n(display \"name> \")\n(io/flush)\n(define name (io/read-line))\n```"
},
{
"name": "io/print-error",
"module": "file-io",
"section": "Console I/O",
"summary": "Print to stderr without a trailing newline.",
"examples": [
"(io/print-error \"warning: something happened\")"
],
"body": "Print to stderr without a trailing newline.\n\n```sema\n(io/print-error \"warning: something happened\")\n```"
},
{
"name": "io/println-error",
"module": "file-io",
"section": "Console I/O",
"summary": "Print to stderr with a trailing newline.",
"examples": [
"(io/println-error \"error: file not found\")"
],
"body": "Print to stderr with a trailing newline.\n\n```sema\n(io/println-error \"error: file not found\")\n```"
},
{
"name": "io/read-line",
"module": "file-io",
"section": "Console I/O",
"summary": "Read a line of input from stdin (trailing `\\n` / `\\r\\n` stripped).",
"examples": [
"(define name (io/read-line))",
"(let loop ()\n (let ((line (io/read-line)))\n (cond\n ((nil? line) (println \"(eof)\"))\n ((= line \"\") (loop)) ; blank line, keep reading\n (else (println \"got: \" line) (loop)))))"
],
"body": "Read a line of input from stdin (trailing `\\n` / `\\r\\n` stripped).\n\n```sema\n(define name (io/read-line))\n```\n\nReturns `nil` when stdin is closed (Ctrl-D in cooked mode, end of a piped file). Use this to distinguish \"user pressed Enter on an empty line\" (returns `\"\"`) from \"stdin is exhausted\" (returns `nil`).\n\n```sema\n(let loop ()\n (let ((line (io/read-line)))\n (cond\n ((nil? line) (println \"(eof)\"))\n ((= line \"\") (loop)) ; blank line, keep reading\n (else (println \"got: \" line) (loop)))))\n```\n\nPreviously `io/read-line` returned `\"\"` on both EOF and empty input, making them indistinguishable. It now returns `nil` on EOF. If you don't want to refactor for this, use `io/eof?` after the call instead."
},
{
"name": "io/read-many",
"module": "file-io",
"section": "Console I/O",
"summary": "Parse a string of Sema source into a list of every top-level datum it contains (alias of `read-many`). Unlike `io/read-line`, this does not touch stdin — it runs the reader over the given string and returns the parsed S-expressions.",
"params": [
{
"name": "source",
"type": "string"
}
],
"returns": "list",
"examples": [
"(io/read-many \"(+ 1 2) (* 3 4)\") ; => ((+ 1 2) (* 3 4))"
],
"body": "Parse a string of Sema source into a list of every top-level datum it contains (alias of `read-many`). Unlike `io/read-line`, this does not touch stdin — it runs the reader over the given string and returns the parsed S-expressions.\n\n```sema\n(io/read-many \"(+ 1 2) (* 3 4)\") ; => ((+ 1 2) (* 3 4))\n```"
},
{
"name": "io/read-stdin",
"module": "file-io",
"section": "Console I/O",
"summary": "Read all of stdin as a string (until EOF).",
"examples": [
"(define input (io/read-stdin))"
],
"body": "Read all of stdin as a string (until EOF).\n\n```sema\n(define input (io/read-stdin))\n```"
},
{
"name": "newline",
"module": "file-io",
"section": "Console I/O",
"summary": "Print a newline character.",
"examples": [
"(newline)"
],
"body": "Print a newline character.\n\n```sema\n(newline)\n```"
},
{
"name": "path/absolute",
"module": "file-io",
"section": "Path Manipulation",
"summary": "Return the absolute path.",
"examples": [
"(path/absolute \".\") ; => \"/full/path/to/current/dir\""
],
"body": "Return the absolute path.\n\n```sema\n(path/absolute \".\") ; => \"/full/path/to/current/dir\"\n```"
},
{
"name": "path/absolute?",
"module": "file-io",
"section": "Path Manipulation",
"summary": "Test if a path is absolute.",
"examples": [
"(path/absolute? \"/usr/bin\") ; => #t\n(path/absolute? \"relative\") ; => #f"
],
"body": "Test if a path is absolute.\n\n```sema\n(path/absolute? \"/usr/bin\") ; => #t\n(path/absolute? \"relative\") ; => #f\n```"
},
{
"name": "path/dir",
"aliases": [
"path/dirname"
],
"module": "file-io",
"section": "Path Manipulation",
"summary": "Return the directory portion of a path. Returns `\"\"` when the path has no parent component.",
"examples": [
"(path/dir \"/a/b/c.txt\") ;; => \"/a/b\"\n(path/dir \"foo\") ;; => \"\""
],
"body": "Return the directory portion of a path. Returns `\"\"` when the path has no parent component.\n\n```sema\n(path/dir \"/a/b/c.txt\") ;; => \"/a/b\"\n(path/dir \"foo\") ;; => \"\"\n```\n\n`path/dirname` is a legacy alias for `path/dir` — same implementation, same return value."
},
{
"name": "path/extension",
"aliases": [
"path/ext"
],
"module": "file-io",
"section": "Path Manipulation",
"summary": "Return the file extension (without the dot). Returns `\"\"` when the path has no extension.",
"examples": [
"(path/extension \"file.rs\") ;; => \"rs\"\n(path/extension \"file.tar.gz\") ;; => \"gz\"\n(path/extension \"Makefile\") ;; => \"\"\n(path/extension \".hidden\") ;; => \"\""
],
"body": "Return the file extension (without the dot). Returns `\"\"` when the path has no extension.\n\n```sema\n(path/extension \"file.rs\") ;; => \"rs\"\n(path/extension \"file.tar.gz\") ;; => \"gz\"\n(path/extension \"Makefile\") ;; => \"\"\n(path/extension \".hidden\") ;; => \"\"\n```\n\n`path/ext` is a legacy alias for `path/extension` — same implementation, same return value.\n\nPrevious versions registered `path/dirname`, `path/basename`, and `path/extension` as independent functions that returned `nil` on the no-parent / no-filename / no-extension case. As of the current release, all six names share one implementation per concept and consistently return `\"\"` (matching `path/dir`, `path/filename`, `path/ext`)."
},
{
"name": "path/filename",
"aliases": [
"path/basename"
],
"module": "file-io",
"section": "Path Manipulation",
"summary": "Return the filename portion of a path. Returns `\"\"` when there is no filename component (e.g. for `\"\"`).",
"examples": [
"(path/filename \"/a/b/c.txt\") ;; => \"c.txt\"\n(path/filename \"plain.rs\") ;; => \"plain.rs\""
],
"body": "Return the filename portion of a path. Returns `\"\"` when there is no filename component (e.g. for `\"\"`).\n\n```sema\n(path/filename \"/a/b/c.txt\") ;; => \"c.txt\"\n(path/filename \"plain.rs\") ;; => \"plain.rs\"\n```\n\n`path/basename` is a legacy alias for `path/filename` — same implementation, same return value."
},
{
"name": "path/join",
"module": "file-io",
"section": "Path Manipulation",
"summary": "Join path components.",
"examples": [
"(path/join \"src\" \"main.rs\") ; => \"src/main.rs\"\n(path/join \"a\" \"b\" \"c.txt\") ; => \"a/b/c.txt\""
],
"body": "Join path components.\n\n```sema\n(path/join \"src\" \"main.rs\") ; => \"src/main.rs\"\n(path/join \"a\" \"b\" \"c.txt\") ; => \"a/b/c.txt\"\n```"
},
{
"name": "path/stem",
"module": "file-io",
"section": "Path Manipulation",
"summary": "Return the filename without extension.",
"examples": [
"(path/stem \"file.rs\") ; => \"file\"\n(path/stem \"archive.tar.gz\") ; => \"archive.tar\""
],
"body": "Return the filename without extension.\n\n```sema\n(path/stem \"file.rs\") ; => \"file\"\n(path/stem \"archive.tar.gz\") ; => \"archive.tar\"\n```"
},
{
"name": "pprint",
"module": "file-io",
"section": "Console I/O",
"summary": "Pretty-print a value to standard output (followed by a newline), wrapping nested structures to roughly 80 columns. Returns `nil`.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "nil",
"examples": [
"(pprint {:a 1 :b 2}) ; prints {:a 1 :b 2}"
],
"body": "Pretty-print a value to standard output (followed by a newline), wrapping nested structures to roughly 80 columns. Returns `nil`.\n\n```sema\n(pprint {:a 1 :b 2}) ; prints {:a 1 :b 2}\n```"
},
{
"name": "print",
"module": "file-io",
"section": "Console I/O",
"summary": "Write values in read-syntax form (strings are quoted) like Scheme's `write`. No trailing newline. Use `display` for human-readable output without quotes.",
"examples": [
"(print \"hello\") ;; outputs: \"hello\"\n(display \"hello\") ;; outputs: hello"
],
"body": "Write values in read-syntax form (strings are quoted) like Scheme's `write`. No trailing newline. Use `display` for human-readable output without quotes.\n\n```sema\n(print \"hello\") ;; outputs: \"hello\"\n(display \"hello\") ;; outputs: hello\n```"
},
{
"name": "print-error",
"module": "file-io",
"section": "Console I/O",
"summary": "Write its arguments to standard error, separated by spaces, with no trailing newline, then flush. Alias: `io/print-error`.",
"returns": "nil",
"examples": [
"(print-error \"warning:\" \"disk full\") ; writes \"warning: disk full\" to stderr"
],
"body": "Write its arguments to standard error, separated by spaces, with no trailing newline, then flush. Alias: `io/print-error`.\n\n```sema\n(print-error \"warning:\" \"disk full\") ; writes \"warning: disk full\" to stderr\n```"
},
{
"name": "println",
"module": "file-io",
"section": "Console I/O",
"summary": "Print a value followed by a newline.",
"examples": [
"(println \"with newline\")\n(println 42)"
],
"body": "Print a value followed by a newline.\n\n```sema\n(println \"with newline\")\n(println 42)\n```"
},
{
"name": "println-error",
"module": "file-io",
"section": "Console I/O",
"summary": "Write its arguments to standard error, separated by spaces, followed by a newline. Alias: `io/println-error`.",
"returns": "nil",
"examples": [
"(println-error \"error:\" 42) ; writes \"error: 42\\n\" to stderr"
],
"body": "Write its arguments to standard error, separated by spaces, followed by a newline. Alias: `io/println-error`.\n\n```sema\n(println-error \"error:\" 42) ; writes \"error: 42\\n\" to stderr\n```"
},
{
"name": "read",
"module": "file-io",
"section": "Reader",
"summary": "Parse a string containing a single Sema expression and return it as data (unevaluated).",
"params": [
{
"name": "s",
"type": "string"
}
],
"examples": [
"(read \"(+ 1 2)\") ; => (+ 1 2)\n(read \"42\") ; => 42"
],
"body": "Parse a string containing a single Sema expression and return it as data (unevaluated).\n\n```sema\n(read \"(+ 1 2)\") ; => (+ 1 2)\n(read \"42\") ; => 42\n```"
},
{
"name": "read-line",
"module": "file-io",
"section": "Console I/O",
"summary": "Read one line from standard input, with the trailing newline removed. Returns `nil` at end of input. Alias: `io/read-line`.",
"returns": "string | nil",
"examples": [
"(read-line) ; => \"user typed text\""
],
"body": "Read one line from standard input, with the trailing newline removed. Returns `nil` at end of input. Alias: `io/read-line`.\n\n```sema\n(read-line) ; => \"user typed text\"\n```"
},
{
"name": "read-many",
"module": "file-io",
"section": "Reader",
"summary": "Parse a string containing zero or more Sema expressions and return them as a list of data values (unevaluated). Alias: `io/read-many`.",
"params": [
{
"name": "s",
"type": "string"
}
],
"returns": "list",
"examples": [
"(read-many \"1 2 3\") ; => (1 2 3)"
],
"body": "Parse a string containing zero or more Sema expressions and return them as a list of data values (unevaluated). Alias: `io/read-many`.\n\n```sema\n(read-many \"1 2 3\") ; => (1 2 3)\n```"
},
{
"name": "read-stdin",
"module": "file-io",
"section": "Console I/O",
"summary": "Read all of standard input to end-of-file and return it as a single string. Alias: `io/read-stdin`.",
"returns": "string",
"examples": [
"(read-stdin) ; => entire piped stdin contents as a string"
],
"body": "Read all of standard input to end-of-file and return it as a single string. Alias: `io/read-stdin`.\n\n```sema\n(read-stdin) ; => entire piped stdin contents as a string\n```"
},
{
"name": "http/delete",
"module": "http-json",
"section": "HTTP",
"summary": "Make an HTTP DELETE request.",
"examples": [
"(http/delete \"https://api.example.com/users/42\"\n {:headers {\"Authorization\" \"Bearer tok_abc123\"}})"
],
"body": "```\n(http/delete url)\n(http/delete url opts)\n```\n\nMake an HTTP DELETE request.\n\n- **url** — string, the request URL\n- **opts** — optional map with `:headers` and/or `:timeout`\n\n```sema\n(http/delete \"https://api.example.com/users/42\"\n {:headers {\"Authorization\" \"Bearer tok_abc123\"}})\n```"
},
{
"name": "http/get",
"module": "http-json",
"section": "HTTP",
"summary": "Make an HTTP GET request.",
"examples": [
";; Simple GET\n(http/get \"https://httpbin.org/get\")\n\n;; GET with custom headers\n(http/get \"https://api.example.com/users\"\n {:headers {:authorization \"Bearer my-token\"}})"
],
"body": "```\n(http/get url)\n(http/get url opts)\n```\n\nMake an HTTP GET request.\n\n- **url** — string, the request URL\n- **opts** — optional map with `:headers` and/or `:timeout`\n\n```sema\n;; Simple GET\n(http/get \"https://httpbin.org/get\")\n\n;; GET with custom headers\n(http/get \"https://api.example.com/users\"\n {:headers {:authorization \"Bearer my-token\"}})\n```"
},
{
"name": "http/post",
"module": "http-json",
"section": "HTTP",
"summary": "Make an HTTP POST request.",
"examples": [
";; POST with a map body (auto-JSON-encoded)\n(http/post \"https://httpbin.org/post\"\n {:name \"Ada\" :age 36})\n\n;; POST with string body and custom headers\n(http/post \"https://api.example.com/webhook\"\n \"raw payload\"\n {:headers {\"Content-Type\" \"text/plain\"}})\n\n;; POST with JSON body and auth\n(http/post \"https://api.example.com/users\"\n {:name \"Ada\" :role \"admin\"}\n {:headers {\"Authorization\" \"Bearer tok_abc123\"}\n :timeout 10000})"
],
"body": "```\n(http/post url body)\n(http/post url body opts)\n```\n\nMake an HTTP POST request.\n\n- **url** — string, the request URL\n- **body** — request body: a string (sent as-is) or a map (auto-encoded as JSON with `Content-Type: application/json`)\n- **opts** — optional map with `:headers` and/or `:timeout`\n\n```sema\n;; POST with a map body (auto-JSON-encoded)\n(http/post \"https://httpbin.org/post\"\n {:name \"Ada\" :age 36})\n\n;; POST with string body and custom headers\n(http/post \"https://api.example.com/webhook\"\n \"raw payload\"\n {:headers {\"Content-Type\" \"text/plain\"}})\n\n;; POST with JSON body and auth\n(http/post \"https://api.example.com/users\"\n {:name \"Ada\" :role \"admin\"}\n {:headers {\"Authorization\" \"Bearer tok_abc123\"}\n :timeout 10000})\n```"
},
{
"name": "http/put",
"module": "http-json",
"section": "HTTP",
"summary": "Make an HTTP PUT request. Behaves identically to `http/post` — map bodies are auto-JSON-encoded.",
"examples": [
"(http/put \"https://api.example.com/users/42\"\n {:name \"Ada Lovelace\" :role \"admin\"})"
],
"body": "```\n(http/put url body)\n(http/put url body opts)\n```\n\nMake an HTTP PUT request. Behaves identically to `http/post` — map bodies are auto-JSON-encoded.\n\n- **url** — string, the request URL\n- **body** — request body (string or map)\n- **opts** — optional map with `:headers` and/or `:timeout`\n\n```sema\n(http/put \"https://api.example.com/users/42\"\n {:name \"Ada Lovelace\" :role \"admin\"})\n```"
},
{
"name": "http/request",
"module": "http-json",
"section": "HTTP",
"summary": "Make an HTTP request with any method. Use this for methods not covered by the convenience functions (e.g., `PATCH`, `HEAD`).",
"examples": [
";; PATCH request\n(http/request \"PATCH\" \"https://api.example.com/users/42\"\n {:headers {\"Content-Type\" \"application/json\"}}\n {:name \"Updated Name\"})\n\n;; HEAD request (body will be empty)\n(define resp (http/request \"HEAD\" \"https://example.com\"))\n(:status resp) ; => 200\n(:body resp) ; => \"\""
],
"body": "```\n(http/request method url)\n(http/request method url opts)\n(http/request method url opts body)\n```\n\nMake an HTTP request with any method. Use this for methods not covered by the convenience functions (e.g., `PATCH`, `HEAD`).\n\n- **method** — string, HTTP method (case-insensitive, converted to uppercase). Supported: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`\n- **url** — string, the request URL\n- **opts** — optional map with `:headers` and/or `:timeout`\n- **body** — optional request body (string or map)\n\n```sema\n;; PATCH request\n(http/request \"PATCH\" \"https://api.example.com/users/42\"\n {:headers {\"Content-Type\" \"application/json\"}}\n {:name \"Updated Name\"})\n\n;; HEAD request (body will be empty)\n(define resp (http/request \"HEAD\" \"https://example.com\"))\n(:status resp) ; => 200\n(:body resp) ; => \"\"\n```"
},
{
"name": "json/decode",
"module": "http-json",
"section": "JSON",
"summary": "Decode a JSON string into a Sema value. JSON objects become maps with keyword keys, arrays become lists. See the [type mapping table](#decoding-json-sema) for full details.",
"examples": [
"(json/decode \"42\") ; => 42\n(json/decode \"3.14\") ; => 3.14\n(json/decode \"\\\"hello\\\"\") ; => \"hello\"\n(json/decode \"true\") ; => #t\n(json/decode \"null\") ; => nil\n(json/decode \"[1, 2, 3]\") ; => (1 2 3)\n(json/decode \"{\\\"name\\\": \\\"Ada\\\"}\") ; => {:name \"Ada\"}",
";; Invalid JSON throws an error\n(json/decode \"not json\") ; Error: json/decode: expected value at line 1 column 1\n\n;; Argument must be a string\n(json/decode 42) ; Error: type error: expected string, got int"
],
"body": "```\n(json/decode json-string) → value\n```\n\nDecode a JSON string into a Sema value. JSON objects become maps with keyword keys, arrays become lists. See the [type mapping table](#decoding-json-sema) for full details.\n\n- **json-string** — a string containing valid JSON\n\n```sema\n(json/decode \"42\") ; => 42\n(json/decode \"3.14\") ; => 3.14\n(json/decode \"\\\"hello\\\"\") ; => \"hello\"\n(json/decode \"true\") ; => #t\n(json/decode \"null\") ; => nil\n(json/decode \"[1, 2, 3]\") ; => (1 2 3)\n(json/decode \"{\\\"name\\\": \\\"Ada\\\"}\") ; => {:name \"Ada\"}\n```\n\nDecoding errors:\n\n```sema\n;; Invalid JSON throws an error\n(json/decode \"not json\") ; Error: json/decode: expected value at line 1 column 1\n\n;; Argument must be a string\n(json/decode 42) ; Error: type error: expected string, got int\n```"
},
{
"name": "json/encode",
"module": "http-json",
"section": "JSON",
"summary": "Encode a Sema value as a compact JSON string. Uses **strict** conversion — errors on values that cannot be represented in JSON (functions, records, NaN, Infinity).",
"examples": [
"(json/encode 42) ; => \"42\"\n(json/encode \"hello\") ; => \"\\\"hello\\\"\"\n(json/encode #t) ; => \"true\"\n(json/encode nil) ; => \"null\"\n(json/encode '(1 2 3)) ; => \"[1,2,3]\"\n(json/encode [1 2 3]) ; => \"[1,2,3]\"\n(json/encode {:name \"Ada\" :age 36}) ; => \"{\\\"age\\\":36,\\\"name\\\":\\\"Ada\\\"}\"",
";; NaN and Infinity cannot be represented in JSON\n(json/encode (/ 0.0 0.0)) ; Error: cannot encode NaN/Infinity as JSON\n\n;; Functions cannot be encoded\n(json/encode println) ; Error: cannot encode native-fn as JSON"
],
"body": "```\n(json/encode value) → string\n```\n\nEncode a Sema value as a compact JSON string. Uses **strict** conversion — errors on values that cannot be represented in JSON (functions, records, NaN, Infinity).\n\n- **value** — any JSON-encodable Sema value\n\n```sema\n(json/encode 42) ; => \"42\"\n(json/encode \"hello\") ; => \"\\\"hello\\\"\"\n(json/encode #t) ; => \"true\"\n(json/encode nil) ; => \"null\"\n(json/encode '(1 2 3)) ; => \"[1,2,3]\"\n(json/encode [1 2 3]) ; => \"[1,2,3]\"\n(json/encode {:name \"Ada\" :age 36}) ; => \"{\\\"age\\\":36,\\\"name\\\":\\\"Ada\\\"}\"\n```\n\nEncoding errors:\n\n```sema\n;; NaN and Infinity cannot be represented in JSON\n(json/encode (/ 0.0 0.0)) ; Error: cannot encode NaN/Infinity as JSON\n\n;; Functions cannot be encoded\n(json/encode println) ; Error: cannot encode native-fn as JSON\n```"
},
{
"name": "json/encode-pretty",
"module": "http-json",
"section": "JSON",
"summary": "Encode a Sema value as a pretty-printed JSON string with 2-space indentation. Same strict conversion rules as `json/encode`.",
"examples": [
"(json/encode-pretty {:name \"Ada\" :scores [95 87 92]})\n;; =>\n;; {\n;; \"name\": \"Ada\",\n;; \"scores\": [\n;; 95,\n;; 87,\n;; 92\n;; ]\n;; }"
],
"body": "```\n(json/encode-pretty value) → string\n```\n\nEncode a Sema value as a pretty-printed JSON string with 2-space indentation. Same strict conversion rules as `json/encode`.\n\n- **value** — any JSON-encodable Sema value\n\n```sema\n(json/encode-pretty {:name \"Ada\" :scores [95 87 92]})\n;; =>\n;; {\n;; \"name\": \"Ada\",\n;; \"scores\": [\n;; 95,\n;; 87,\n;; 92\n;; ]\n;; }\n```"
},
{
"name": "kv/close",
"module": "kv-store",
"section": "Functions",
"summary": "Close a store, flushing data and freeing the handle. Returns `nil`.",
"examples": [
"(kv/close \"config\")"
],
"body": "Close a store, flushing data and freeing the handle. Returns `nil`.\n\n```sema\n(kv/close \"config\")\n```\n\nData is safe even without calling `kv/close` (every write already flushes), but closing frees memory and releases the store name."
},
{
"name": "kv/delete",
"module": "kv-store",
"section": "Functions",
"summary": "Delete a key. Returns `#t` if the key existed, `#f` otherwise. Flushes to disk immediately.",
"examples": [
"(kv/delete \"config\" \"api-key\") ; => #t\n(kv/delete \"config\" \"api-key\") ; => #f (already deleted)"
],
"body": "Delete a key. Returns `#t` if the key existed, `#f` otherwise. Flushes to disk immediately.\n\n```sema\n(kv/delete \"config\" \"api-key\") ; => #t\n(kv/delete \"config\" \"api-key\") ; => #f (already deleted)\n```"
},
{
"name": "kv/get",
"module": "kv-store",
"section": "Functions",
"summary": "Get a value by key. Returns `nil` if the key doesn't exist.",
"examples": [
"(kv/get \"config\" \"api-key\") ; => \"sk-...\" or nil"
],
"body": "Get a value by key. Returns `nil` if the key doesn't exist.\n\n```sema\n(kv/get \"config\" \"api-key\") ; => \"sk-...\" or nil\n```"
},
{
"name": "kv/keys",
"module": "kv-store",
"section": "Functions",
"summary": "List all keys in the store. Returns a list of strings.",
"examples": [
"(kv/keys \"config\") ; => (\"api-key\" \"retries\" \"tags\")"
],
"body": "List all keys in the store. Returns a list of strings.\n\n```sema\n(kv/keys \"config\") ; => (\"api-key\" \"retries\" \"tags\")\n```"
},
{
"name": "kv/open",
"module": "kv-store",
"section": "Functions",
"summary": "Open (or create) a named KV store backed by a JSON file. If the file exists, its contents are loaded. Returns the store name.",
"examples": [
"(kv/open \"config\" \"/path/to/config.json\") ; => \"config\"\n(kv/open \"cache\" \"cache.json\") ; relative to CWD"
],
"body": "Open (or create) a named KV store backed by a JSON file. If the file exists, its contents are loaded. Returns the store name.\n\n```sema\n(kv/open \"config\" \"/path/to/config.json\") ; => \"config\"\n(kv/open \"cache\" \"cache.json\") ; relative to CWD\n```\n\nIf the file doesn't exist yet, no file is created — that happens on the first `kv/set`."
},
{
"name": "kv/set",
"module": "kv-store",
"section": "Functions",
"summary": "Set a key-value pair. The value is serialized as JSON. Returns the value. Flushes to disk immediately.",
"examples": [
"(kv/set \"config\" \"api-key\" \"sk-...\")\n(kv/set \"config\" \"retries\" 3)\n(kv/set \"config\" \"tags\" '(\"a\" \"b\" \"c\"))\n(kv/set \"config\" \"user\" {:name \"Alice\" :role \"admin\"})"
],
"body": "Set a key-value pair. The value is serialized as JSON. Returns the value. Flushes to disk immediately.\n\n```sema\n(kv/set \"config\" \"api-key\" \"sk-...\")\n(kv/set \"config\" \"retries\" 3)\n(kv/set \"config\" \"tags\" '(\"a\" \"b\" \"c\"))\n(kv/set \"config\" \"user\" {:name \"Alice\" :role \"admin\"})\n```"
},
{
"name": "any",
"module": "lists",
"section": "Searching",
"summary": "Test if any element satisfies a predicate.",
"examples": [
"(any even? '(1 3 5 6)) ; => #t\n(any even? '(1 3 5)) ; => #f"
],
"body": "Test if any element satisfies a predicate.\n\n```sema\n(any even? '(1 3 5 6)) ; => #t\n(any even? '(1 3 5)) ; => #f\n```"
},
{
"name": "any?",
"module": "lists",
"section": "Searching & Testing",
"summary": "Return `#t` if `pred` returns truthy for at least one element of `seq`. Alias of `any` (and of `some?`).",
"params": [
{
"name": "pred",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "bool",
"examples": [
"(any? odd? '(2 4 5 6)) ; => #t\n(any? odd? '(2 4 6)) ; => #f"
],
"body": "Return `#t` if `pred` returns truthy for at least one element of `seq`. Alias of `any` (and of `some?`).\n\n```sema\n(any? odd? '(2 4 5 6)) ; => #t\n(any? odd? '(2 4 6)) ; => #f\n```"
},
{
"name": "append",
"module": "lists",
"section": "Basic Operations",
"summary": "Concatenate lists.",
"examples": [
"(append '(1 2) '(3 4)) ; => (1 2 3 4)\n(append '(1) '(2) '(3)) ; => (1 2 3)"
],
"body": "Concatenate lists.\n\n```sema\n(append '(1 2) '(3 4)) ; => (1 2 3 4)\n(append '(1) '(2) '(3)) ; => (1 2 3)\n```"
},
{
"name": "apply",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Apply a function to a list of arguments.",
"examples": [
"(apply + '(1 2 3)) ; => 6\n(apply max '(3 1 4)) ; => 4"
],
"body": "Apply a function to a list of arguments.\n\n```sema\n(apply + '(1 2 3)) ; => 6\n(apply max '(3 1 4)) ; => 4\n```"
},
{
"name": "assoc",
"module": "lists",
"section": "Association Lists",
"summary": "Look up a key in an association list (list of pairs). Uses `equal?` comparison.",
"examples": [
"(define alist '((\"a\" 1) (\"b\" 2) (\"c\" 3)))\n(assoc \"b\" alist) ; => (\"b\" 2)\n(assoc \"z\" alist) ; => #f"
],
"body": "Look up a key in an association list (list of pairs). Uses `equal?` comparison.\n\n```sema\n(define alist '((\"a\" 1) (\"b\" 2) (\"c\" 3)))\n(assoc \"b\" alist) ; => (\"b\" 2)\n(assoc \"z\" alist) ; => #f\n```"
},
{
"name": "assq",
"module": "lists",
"section": "Association Lists",
"summary": "Like `assoc` but uses `eq?` comparison (pointer/symbol equality).",
"examples": [
"(assq 'b '((a 1) (b 2))) ; => (b 2)"
],
"body": "Like `assoc` but uses `eq?` comparison (pointer/symbol equality).\n\n```sema\n(assq 'b '((a 1) (b 2))) ; => (b 2)\n```"
},
{
"name": "assv",
"module": "lists",
"section": "Association Lists",
"summary": "Like `assoc` but uses `eqv?` comparison (value equality for numbers).",
"examples": [
"(assv 2 '((1 \"one\") (2 \"two\"))) ; => (2 \"two\")"
],
"body": "Like `assoc` but uses `eqv?` comparison (value equality for numbers).\n\n```sema\n(assv 2 '((1 \"one\") (2 \"two\"))) ; => (2 \"two\")\n```"
},
{
"name": "caaar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(car (car (car x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(caaar '(((1 2) 3) 4)) ; => 1"
],
"body": "Equivalent to `(car (car (car x)))`.\n\n```sema\n(caaar '(((1 2) 3) 4)) ; => 1\n```"
},
{
"name": "caadr",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(car (car (cdr x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(caadr '(1 (2 3) 4)) ; => 2"
],
"body": "Equivalent to `(car (car (cdr x)))`.\n\n```sema\n(caadr '(1 (2 3) 4)) ; => 2\n```"
},
{
"name": "caar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(car (car x))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(caar '((1 2) 3)) ; => 1"
],
"body": "Equivalent to `(car (car x))`.\n\n```sema\n(caar '((1 2) 3)) ; => 1\n```"
},
{
"name": "cadar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(car (cdr (car x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cadar '((1 2 3) 4)) ; => 2"
],
"body": "Equivalent to `(car (cdr (car x)))`.\n\n```sema\n(cadar '((1 2 3) 4)) ; => 2\n```"
},
{
"name": "cadr",
"aliases": [
"caddr"
],
"module": "lists",
"section": "Construction & Access",
"summary": "Compositions of `car` and `cdr`. Available: `caar`, `cadr`, `cdar`, `cddr`, `caaar`, `caadr`, `cadar`, `caddr`, `cdaar`, `cdadr`, `cddar`, `cdddr`.",
"examples": [
"(cadr '(1 2 3)) ; => 2\n(caddr '(1 2 3)) ; => 3"
],
"body": "Compositions of `car` and `cdr`. Available: `caar`, `cadr`, `cdar`, `cddr`, `caaar`, `caadr`, `cadar`, `caddr`, `cdaar`, `cdadr`, `cddar`, `cdddr`.\n\n```sema\n(cadr '(1 2 3)) ; => 2\n(caddr '(1 2 3)) ; => 3\n```"
},
{
"name": "car",
"module": "lists",
"section": "Construction & Access",
"summary": "Return the first element of a list.",
"examples": [
"(car '(1 2 3)) ; => 1"
],
"body": "Return the first element of a list.\n\n```sema\n(car '(1 2 3)) ; => 1\n```"
},
{
"name": "cdaar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (car (car x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cdaar '(((1 2) 3) 4)) ; => (2)"
],
"body": "Equivalent to `(cdr (car (car x)))`.\n\n```sema\n(cdaar '(((1 2) 3) 4)) ; => (2)\n```"
},
{
"name": "cdadr",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (car (cdr x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cdadr '(1 (2 3) 4)) ; => (3)"
],
"body": "Equivalent to `(cdr (car (cdr x)))`.\n\n```sema\n(cdadr '(1 (2 3) 4)) ; => (3)\n```"
},
{
"name": "cdar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (car x))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cdar '((1 2 3) 4)) ; => (2 3)"
],
"body": "Equivalent to `(cdr (car x))`.\n\n```sema\n(cdar '((1 2 3) 4)) ; => (2 3)\n```"
},
{
"name": "cddar",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (cdr (car x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cddar '((1 2 3) 4)) ; => (3)"
],
"body": "Equivalent to `(cdr (cdr (car x)))`.\n\n```sema\n(cddar '((1 2 3) 4)) ; => (3)\n```"
},
{
"name": "cdddr",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (cdr (cdr x)))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cdddr '(1 2 3 4 5)) ; => (4 5)"
],
"body": "Equivalent to `(cdr (cdr (cdr x)))`.\n\n```sema\n(cdddr '(1 2 3 4 5)) ; => (4 5)\n```"
},
{
"name": "cddr",
"module": "lists",
"section": "Construction & Access",
"summary": "Equivalent to `(cdr (cdr x))`.",
"params": [
{
"name": "x",
"type": "list"
}
],
"examples": [
"(cddr '(1 2 3 4)) ; => (3 4)"
],
"body": "Equivalent to `(cdr (cdr x))`.\n\n```sema\n(cddr '(1 2 3 4)) ; => (3 4)\n```"
},
{
"name": "cdr",
"module": "lists",
"section": "Construction & Access",
"summary": "Return the rest of a list (everything after the first element).",
"examples": [
"(cdr '(1 2 3)) ; => (2 3)\n(cdr '(1)) ; => ()"
],
"body": "Return the rest of a list (everything after the first element).\n\n```sema\n(cdr '(1 2 3)) ; => (2 3)\n(cdr '(1)) ; => ()\n```\n\n`car` and `cdr` are inherited from the [IBM 704](http://bitsavers.informatik.uni-stuttgart.de/pdf/ibm/704/24-6661-2_704_Manual_1955.pdf) (1955), the machine Lisp was originally implemented on. The 704 stored cons cells in a single 36-bit word, with two 15-bit pointer fields: the **address** field (bits 21-35) pointed to the first element, and the **decrement** field (bits 3-17) pointed to the rest of the list. `car` stands for \"Contents of the Address Register\" and `cdr` for \"Contents of the Decrement Register\" — they were single hardware instructions that extracted these sub-fields. Sema also provides `first`/`rest` as more readable aliases."
},
{
"name": "cons",
"module": "lists",
"section": "Construction & Access",
"summary": "Prepend an element to a list.",
"examples": [
"(cons 0 '(1 2 3)) ; => (0 1 2 3)\n(cons 1 '()) ; => (1)"
],
"body": "Prepend an element to a list.\n\n```sema\n(cons 0 '(1 2 3)) ; => (0 1 2 3)\n(cons 1 '()) ; => (1)\n```"
},
{
"name": "drop",
"module": "lists",
"section": "Sublists",
"summary": "Drop the first N elements.",
"examples": [
"(drop 2 '(1 2 3 4 5)) ; => (3 4 5)"
],
"body": "Drop the first N elements.\n\n```sema\n(drop 2 '(1 2 3 4 5)) ; => (3 4 5)\n```"
},
{
"name": "drop-while",
"module": "lists",
"section": "Slicing",
"summary": "Drop the leading elements of `seq` for which `pred` is truthy, returning the rest unchanged. Same as `list/drop-while`.",
"params": [
{
"name": "pred",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "list",
"examples": [
"(drop-while (fn (x) (< x 4)) '(1 2 3 4 1)) ; => (4 1)"
],
"body": "Drop the leading elements of `seq` for which `pred` is truthy, returning the rest unchanged. Same as `list/drop-while`.\n\n```sema\n(drop-while (fn (x) (< x 4)) '(1 2 3 4 1)) ; => (4 1)\n```"
},
{
"name": "every",
"module": "lists",
"section": "Searching",
"summary": "Test if all elements satisfy a predicate.",
"examples": [
"(every even? '(2 4 6)) ; => #t\n(every even? '(2 3 6)) ; => #f"
],
"body": "Test if all elements satisfy a predicate.\n\n```sema\n(every even? '(2 4 6)) ; => #t\n(every even? '(2 3 6)) ; => #f\n```"
},
{
"name": "every?",
"module": "lists",
"section": "Searching & Testing",
"summary": "Return `#t` if `pred` returns truthy for every element of `seq` (and for the empty sequence). Alias of `every`.",
"params": [
{
"name": "pred",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "bool",
"examples": [
"(every? odd? '(1 3 5)) ; => #t\n(every? odd? '(1 2 3)) ; => #f"
],
"body": "Return `#t` if `pred` returns truthy for every element of `seq` (and for the empty sequence). Alias of `every`.\n\n```sema\n(every? odd? '(1 3 5)) ; => #t\n(every? odd? '(1 2 3)) ; => #f\n```"
},
{
"name": "filter",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Return elements that satisfy a predicate.",
"examples": [
"(filter even? '(1 2 3 4 5)) ; => (2 4)\n(filter string? '(1 \"a\" 2)) ; => (\"a\")"
],
"body": "Return elements that satisfy a predicate.\n\n```sema\n(filter even? '(1 2 3 4 5)) ; => (2 4)\n(filter string? '(1 \"a\" 2)) ; => (\"a\")\n```"
},
{
"name": "first",
"module": "lists",
"section": "Construction & Access",
"summary": "Alias for `car`. Return the first element.",
"examples": [
"(first '(1 2 3)) ; => 1"
],
"body": "Alias for `car`. Return the first element.\n\n```sema\n(first '(1 2 3)) ; => 1\n```"
},
{
"name": "flat-map",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Map a function over a list and flatten the results by one level.",
"examples": [
"(flat-map (fn (x) (list x (* x 10))) '(1 2 3))\n; => (1 10 2 20 3 30)"
],
"body": "Map a function over a list and flatten the results by one level.\n\n```sema\n(flat-map (fn (x) (list x (* x 10))) '(1 2 3))\n; => (1 10 2 20 3 30)\n```"
},
{
"name": "flatten",
"module": "lists",
"section": "Sublists",
"summary": "Flatten one level of nesting: splice each immediate sublist/vector element into the result. (It is shallow — deeper nesting is preserved.)",
"examples": [
"(flatten '(1 (2 3) 4)) ; => (1 2 3 4)\n(flatten '(1 (2 (3)) 4)) ; => (1 2 (3) 4)"
],
"body": "Flatten one level of nesting: splice each immediate sublist/vector element into the result.\n(It is shallow — deeper nesting is preserved.)\n\n```sema\n(flatten '(1 (2 3) 4)) ; => (1 2 3 4)\n(flatten '(1 (2 (3)) 4)) ; => (1 2 (3) 4)\n```"
},
{
"name": "flatten-deep",
"module": "lists",
"section": "Sublists",
"summary": "Recursively flatten all nested lists.",
"examples": [
"(flatten-deep '(1 (2 (3 (4))))) ; => (1 2 3 4)"
],
"body": "Recursively flatten all nested lists.\n\n```sema\n(flatten-deep '(1 (2 (3 (4))))) ; => (1 2 3 4)\n```"
},
{
"name": "fold",
"module": "lists",
"section": "Reduction",
"summary": "Left fold: combine elements of `seq` from the left starting with `init`, calling `(f acc elem)` for each. Alias of `foldl`.",
"params": [
{
"name": "f",
"type": "function"
},
{
"name": "init",
"type": "any"
},
{
"name": "seq",
"type": "list | vector"
}
],
"examples": [
"(fold + 0 '(1 2 3 4)) ; => 10"
],
"body": "Left fold: combine elements of `seq` from the left starting with `init`, calling `(f acc elem)` for each. Alias of `foldl`.\n\n```sema\n(fold + 0 '(1 2 3 4)) ; => 10\n```"
},
{
"name": "foldl",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Left fold. `(foldl f init list)` accumulates from left to right, calling `(f acc elem)` for each element.",
"examples": [
"(foldl + 0 '(1 2 3 4 5)) ; => 15\n(foldl (fn (acc x) (cons x acc)) '() '(1 2 3)) ; => (3 2 1)"
],
"body": "Left fold. `(foldl f init list)` accumulates from left to right, calling `(f acc elem)` for each\nelement.\n\n```sema\n(foldl + 0 '(1 2 3 4 5)) ; => 15\n(foldl (fn (acc x) (cons x acc)) '() '(1 2 3)) ; => (3 2 1)\n```"
},
{
"name": "foldr",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Right fold. `(foldr f init list)` — accumulates from right to left.",
"examples": [
"(foldr cons '() '(1 2 3)) ; => (1 2 3)"
],
"body": "Right fold. `(foldr f init list)` — accumulates from right to left.\n\n```sema\n(foldr cons '() '(1 2 3)) ; => (1 2 3)\n```"
},
{
"name": "for-each",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Apply a function to each element for side effects.",
"examples": [
"(for-each println '(\"a\" \"b\" \"c\"))\n;; prints: a, b, c (each on a new line)"
],
"body": "Apply a function to each element for side effects.\n\n```sema\n(for-each println '(\"a\" \"b\" \"c\"))\n;; prints: a, b, c (each on a new line)\n```"
},
{
"name": "frequencies",
"module": "lists",
"section": "Grouping",
"summary": "Count occurrences of each element, returning a map.",
"examples": [
"(frequencies '(a b a c b a)) ; => {a 3 b 2 c 1}"
],
"body": "Count occurrences of each element, returning a map.\n\n```sema\n(frequencies '(a b a c b a)) ; => {a 3 b 2 c 1}\n```"
},
{
"name": "interpose",
"module": "lists",
"section": "Grouping",
"summary": "Insert a separator between elements.",
"examples": [
"(interpose \", \" '(\"a\" \"b\" \"c\")) ; => (\"a\" \", \" \"b\" \", \" \"c\")"
],
"body": "Insert a separator between elements.\n\n```sema\n(interpose \", \" '(\"a\" \"b\" \"c\")) ; => (\"a\" \", \" \"b\" \", \" \"c\")\n```"
},
{
"name": "iota",
"module": "lists",
"section": "Construction",
"summary": "Generate a list of numbers. `(iota count)`, `(iota count start)`, or `(iota count start step)`.",
"examples": [
"(iota 5) ; => (0 1 2 3 4)\n(iota 3 10) ; => (10 11 12)\n(iota 4 0 2) ; => (0 2 4 6)"
],
"body": "Generate a list of numbers. `(iota count)`, `(iota count start)`, or `(iota count start step)`.\n\n```sema\n(iota 5) ; => (0 1 2 3 4)\n(iota 3 10) ; => (10 11 12)\n(iota 4 0 2) ; => (0 2 4 6)\n```"
},
{
"name": "last",
"module": "lists",
"section": "Construction & Access",
"summary": "Return the last element of a list.",
"examples": [
"(last '(1 2 3)) ; => 3"
],
"body": "Return the last element of a list.\n\n```sema\n(last '(1 2 3)) ; => 3\n```"
},
{
"name": "length",
"module": "lists",
"section": "Basic Operations",
"summary": "Return the number of elements in a list.",
"examples": [
"(length '(1 2 3)) ; => 3\n(length '()) ; => 0"
],
"body": "Return the number of elements in a list.\n\n```sema\n(length '(1 2 3)) ; => 3\n(length '()) ; => 0\n```"
},
{
"name": "list",
"module": "lists",
"section": "Construction & Access",
"summary": "Create a new list.",
"examples": [
"(list 1 2 3) ; => (1 2 3)\n(list) ; => ()\n(list \"a\" \"b\") ; => (\"a\" \"b\")"
],
"body": "Create a new list.\n\n```sema\n(list 1 2 3) ; => (1 2 3)\n(list) ; => ()\n(list \"a\" \"b\") ; => (\"a\" \"b\")\n```"
},
{
"name": "list/avg",
"module": "lists",
"section": "Statistics",
"summary": "Return the average of a numeric list.",
"examples": [
"(list/avg '(2 4 6)) ; => 4.0"
],
"body": "Return the average of a numeric list.\n\n```sema\n(list/avg '(2 4 6)) ; => 4.0\n```"
},
{
"name": "list/chunk",
"module": "lists",
"section": "Grouping",
"summary": "Split a list into chunks of a given size.",
"examples": [
"(list/chunk 2 '(1 2 3 4 5)) ; => ((1 2) (3 4) (5))\n(list/chunk 3 '(1 2 3 4 5 6)) ; => ((1 2 3) (4 5 6))"
],
"body": "Split a list into chunks of a given size.\n\n```sema\n(list/chunk 2 '(1 2 3 4 5)) ; => ((1 2) (3 4) (5))\n(list/chunk 3 '(1 2 3 4 5 6)) ; => ((1 2 3) (4 5 6))\n```"
},
{
"name": "list/cross-join",
"module": "lists",
"section": "Windowing",
"summary": "Cartesian product of two lists.",
"examples": [
"(list/cross-join '(1 2) '(3 4)) ; => ((1 3) (1 4) (2 3) (2 4))"
],
"body": "Cartesian product of two lists.\n\n```sema\n(list/cross-join '(1 2) '(3 4)) ; => ((1 3) (1 4) (2 3) (2 4))\n```"
},
{
"name": "list/dedupe",
"module": "lists",
"section": "Searching",
"summary": "Remove consecutive duplicates from a list.",
"examples": [
"(list/dedupe '(1 1 2 2 3 3 2)) ; => (1 2 3 2)"
],
"body": "Remove consecutive duplicates from a list.\n\n```sema\n(list/dedupe '(1 1 2 2 3 3 2)) ; => (1 2 3 2)\n```"
},
{
"name": "list/diff",
"module": "lists",
"section": "Set Operations",
"summary": "Return elements in the first list that are not in the second list.",
"examples": [
"(list/diff '(1 2 3 4 5) '(3 4)) ; => (1 2 5)"
],
"body": "Return elements in the first list that are not in the second list.\n\n```sema\n(list/diff '(1 2 3 4 5) '(3 4)) ; => (1 2 5)\n```"
},
{
"name": "list/drop-while",
"module": "lists",
"section": "Splitting",
"summary": "Drop elements from the front while a predicate holds.",
"examples": [
"(list/drop-while (fn (x) (< x 4)) '(1 2 3 4 5)) ; => (4 5)"
],
"body": "Drop elements from the front while a predicate holds.\n\n```sema\n(list/drop-while (fn (x) (< x 4)) '(1 2 3 4 5)) ; => (4 5)\n```"
},
{
"name": "list/duplicates",
"module": "lists",
"section": "Set Operations",
"summary": "Return values that appear more than once in a list.",
"examples": [
"(list/duplicates '(1 2 2 3 3 3 4)) ; => (2 3)"
],
"body": "Return values that appear more than once in a list.\n\n```sema\n(list/duplicates '(1 2 2 3 3 3 4)) ; => (2 3)\n```"
},
{
"name": "list/find",
"module": "lists",
"section": "Filtering",
"summary": "Return the first element that satisfies a predicate, or `nil` if none found.",
"examples": [
"(list/find even? '(1 3 4 5 6)) ; => 4\n(list/find even? '(1 3 5)) ; => nil"
],
"body": "Return the first element that satisfies a predicate, or `nil` if none found.\n\n```sema\n(list/find even? '(1 3 4 5 6)) ; => 4\n(list/find even? '(1 3 5)) ; => nil\n```"
},
{
"name": "list/group-by",
"module": "lists",
"section": "Grouping",
"summary": "Group elements by a function, returning a map.",
"examples": [
"(list/group-by even? '(1 2 3 4 5)) ; => {#f (1 3 5) #t (2 4)}"
],
"body": "Group elements by a function, returning a map.\n\n```sema\n(list/group-by even? '(1 2 3 4 5)) ; => {#f (1 3 5) #t (2 4)}\n```"
},
{
"name": "list/index-of",
"module": "lists",
"section": "Searching",
"summary": "Return the index of the first occurrence of a value, or `nil` if not found.",
"examples": [
"(list/index-of '(10 20 30) 20) ;; => 1\n(list/index-of '(10 20 30) 99) ;; => nil"
],
"body": "Return the index of the first occurrence of a value, or `nil` if not found.\n\n```sema\n(list/index-of '(10 20 30) 20) ;; => 1\n(list/index-of '(10 20 30) 99) ;; => nil\n```"
},
{
"name": "list/interleave",
"module": "lists",
"section": "Grouping",
"summary": "Interleave elements from two lists.",
"examples": [
"(list/interleave '(1 2 3) '(a b c)) ; => (1 a 2 b 3 c)"
],
"body": "Interleave elements from two lists.\n\n```sema\n(list/interleave '(1 2 3) '(a b c)) ; => (1 a 2 b 3 c)\n```"
},
{
"name": "list/intersect",
"module": "lists",
"section": "Set Operations",
"summary": "Return elements present in both lists.",
"examples": [
"(list/intersect '(1 2 3 4 5) '(3 4 6)) ; => (3 4)"
],
"body": "Return elements present in both lists.\n\n```sema\n(list/intersect '(1 2 3 4 5) '(3 4 6)) ; => (3 4)\n```"
},
{
"name": "list/join",
"module": "lists",
"section": "Padding & Joining",
"summary": "Join list elements into a string. Optional final separator.",
"examples": [
"(list/join '(1 2 3) \", \") ; => \"1, 2, 3\"\n(list/join '(1 2 3) \", \" \" and \") ; => \"1, 2 and 3\""
],
"body": "Join list elements into a string. Optional final separator.\n\n```sema\n(list/join '(1 2 3) \", \") ; => \"1, 2, 3\"\n(list/join '(1 2 3) \", \" \" and \") ; => \"1, 2 and 3\"\n```"
},
{
"name": "list/key-by",
"module": "lists",
"section": "Extraction",
"summary": "Transform a list of maps into a map keyed by a function result.",
"examples": [
"(list/key-by (fn (p) (get p :id)) people) ; => map keyed by :id"
],
"body": "Transform a list of maps into a map keyed by a function result.\n\n```sema\n(list/key-by (fn (p) (get p :id)) people) ; => map keyed by :id\n```"
},
{
"name": "list/max",
"module": "lists",
"section": "Aggregation",
"summary": "Return the maximum value in a list.",
"examples": [
"(list/max '(3 1 4 1 5)) ; => 5"
],
"body": "Return the maximum value in a list.\n\n```sema\n(list/max '(3 1 4 1 5)) ; => 5\n```"
},
{
"name": "list/median",
"module": "lists",
"section": "Statistics",
"summary": "Return the statistical median.",
"examples": [
"(list/median '(3 1 2)) ; => 2.0\n(list/median '(1 2 3 4)) ; => 2.5"
],
"body": "Return the statistical median.\n\n```sema\n(list/median '(3 1 2)) ; => 2.0\n(list/median '(1 2 3 4)) ; => 2.5\n```"
},
{
"name": "list/min",
"module": "lists",
"section": "Aggregation",
"summary": "Return the minimum value in a list.",
"examples": [
"(list/min '(3 1 4 1 5)) ; => 1"
],
"body": "Return the minimum value in a list.\n\n```sema\n(list/min '(3 1 4 1 5)) ; => 1\n```"
},
{
"name": "list/mode",
"module": "lists",
"section": "Statistics",
"summary": "Return the most frequent value. If tied, returns a list.",
"examples": [
"(list/mode '(1 2 2 3 3 3)) ; => 3\n(list/mode '(1 1 2 2)) ; => (1 2)"
],
"body": "Return the most frequent value. If tied, returns a list.\n\n```sema\n(list/mode '(1 2 2 3 3 3)) ; => 3\n(list/mode '(1 1 2 2)) ; => (1 2)\n```"
},
{
"name": "list/pad",
"module": "lists",
"section": "Padding & Joining",
"summary": "Pad a list to a target length with a fill value.",
"examples": [
"(list/pad '(1 2 3) 5 0) ; => (1 2 3 0 0)"
],
"body": "Pad a list to a target length with a fill value.\n\n```sema\n(list/pad '(1 2 3) 5 0) ; => (1 2 3 0 0)\n```"
},
{
"name": "list/page",
"module": "lists",
"section": "Windowing",
"summary": "Paginate a list. `(list/page items page per-page)` — 1-indexed pages.",
"examples": [
"(list/page (range 20) 1 5) ; => (0 1 2 3 4)\n(list/page (range 20) 2 5) ; => (5 6 7 8 9)"
],
"body": "Paginate a list. `(list/page items page per-page)` — 1-indexed pages.\n\n```sema\n(list/page (range 20) 1 5) ; => (0 1 2 3 4)\n(list/page (range 20) 2 5) ; => (5 6 7 8 9)\n```"
},
{
"name": "list/pick",
"module": "lists",
"section": "Random",
"summary": "Pick a random element from a list.",
"examples": [
"(list/pick '(1 2 3 4 5)) ; => 3 (varies)"
],
"body": "Pick a random element from a list.\n\n```sema\n(list/pick '(1 2 3 4 5)) ; => 3 (varies)\n```"
},
{
"name": "list/pluck",
"module": "lists",
"section": "Extraction",
"summary": "Extract a specific key from each map in a list.",
"examples": [
"(define people (list {:name \"Alice\" :age 30} {:name \"Bob\" :age 25}))\n(list/pluck :name people) ; => (\"Alice\" \"Bob\")"
],
"body": "Extract a specific key from each map in a list.\n\n```sema\n(define people (list {:name \"Alice\" :age 30} {:name \"Bob\" :age 25}))\n(list/pluck :name people) ; => (\"Alice\" \"Bob\")\n```"
},
{
"name": "list/reject",
"module": "lists",
"section": "Filtering",
"summary": "Return elements that do NOT satisfy a predicate (inverse of `filter`).",
"examples": [
"(list/reject even? '(1 2 3 4 5)) ; => (1 3 5)"
],
"body": "Return elements that do NOT satisfy a predicate (inverse of `filter`).\n\n```sema\n(list/reject even? '(1 2 3 4 5)) ; => (1 3 5)\n```"
},
{
"name": "list/repeat",
"module": "lists",
"section": "Construction",
"summary": "Create a list by repeating a value N times.",
"examples": [
"(list/repeat 3 0) ; => (0 0 0)\n(list/repeat 4 \"x\") ; => (\"x\" \"x\" \"x\" \"x\")"
],
"body": "Create a list by repeating a value N times.\n\n```sema\n(list/repeat 3 0) ; => (0 0 0)\n(list/repeat 4 \"x\") ; => (\"x\" \"x\" \"x\" \"x\")\n```"
},
{
"name": "list/shuffle",
"module": "lists",
"section": "Random",
"summary": "Return a randomly shuffled copy of a list.",
"examples": [
"(list/shuffle '(1 2 3 4 5)) ; => (3 1 5 2 4) (varies)"
],
"body": "Return a randomly shuffled copy of a list.\n\n```sema\n(list/shuffle '(1 2 3 4 5)) ; => (3 1 5 2 4) (varies)\n```"
},
{
"name": "list/sliding",
"module": "lists",
"section": "Windowing",
"summary": "Create a sliding window over a list. Optional step parameter.",
"examples": [
"(list/sliding '(1 2 3 4 5) 2) ; => ((1 2) (2 3) (3 4) (4 5))\n(list/sliding '(1 2 3 4 5 6) 2 3) ; => ((1 2) (4 5))"
],
"body": "Create a sliding window over a list. Optional step parameter.\n\n```sema\n(list/sliding '(1 2 3 4 5) 2) ; => ((1 2) (2 3) (3 4) (4 5))\n(list/sliding '(1 2 3 4 5 6) 2 3) ; => ((1 2) (4 5))\n```"
},
{
"name": "list/sole",
"module": "lists",
"section": "Filtering",
"summary": "Return the single element matching a predicate. Errors if zero or more than one match.",
"examples": [
"(list/sole (fn (x) (> x 4)) '(1 2 3 4 5)) ; => 5"
],
"body": "Return the single element matching a predicate. Errors if zero or more than one match.\n\n```sema\n(list/sole (fn (x) (> x 4)) '(1 2 3 4 5)) ; => 5\n```"
},
{
"name": "list/split-at",
"module": "lists",
"section": "Splitting",
"summary": "Split a list at a given index, returning two lists.",
"examples": [
"(list/split-at '(1 2 3 4 5) 3) ; => ((1 2 3) (4 5))"
],
"body": "Split a list at a given index, returning two lists.\n\n```sema\n(list/split-at '(1 2 3 4 5) 3) ; => ((1 2 3) (4 5))\n```"
},
{
"name": "list/sum",
"module": "lists",
"section": "Aggregation",
"summary": "Sum all numbers in a list.",
"examples": [
"(list/sum '(1 2 3 4 5)) ; => 15"
],
"body": "Sum all numbers in a list.\n\n```sema\n(list/sum '(1 2 3 4 5)) ; => 15\n```"
},
{
"name": "list/take-while",
"module": "lists",
"section": "Splitting",
"summary": "Take elements from the front while a predicate holds.",
"examples": [
"(list/take-while (fn (x) (< x 4)) '(1 2 3 4 5)) ; => (1 2 3)"
],
"body": "Take elements from the front while a predicate holds.\n\n```sema\n(list/take-while (fn (x) (< x 4)) '(1 2 3 4 5)) ; => (1 2 3)\n```"
},
{
"name": "list/times",
"module": "lists",
"section": "Generation",
"summary": "Generate a list by calling a function N times with the index (0-based).",
"examples": [
"(list/times 5 (fn (i) (* i i))) ; => (0 1 4 9 16)"
],
"body": "Generate a list by calling a function N times with the index (0-based).\n\n```sema\n(list/times 5 (fn (i) (* i i))) ; => (0 1 4 9 16)\n```"
},
{
"name": "list/unique",
"module": "lists",
"section": "Searching",
"summary": "Remove duplicate elements, preserving order.",
"examples": [
"(list/unique '(1 2 2 3 3 3)) ; => (1 2 3)"
],
"body": "Remove duplicate elements, preserving order.\n\n```sema\n(list/unique '(1 2 2 3 3 3)) ; => (1 2 3)\n```"
},
{
"name": "make-list",
"module": "lists",
"section": "Construction",
"summary": "Alias for `list/repeat`.",
"examples": [
"(make-list 3 0) ; => (0 0 0)"
],
"body": "Alias for `list/repeat`.\n\n```sema\n(make-list 3 0) ; => (0 0 0)\n```"
},
{
"name": "map",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Apply a function to each element of one or more lists.",
"examples": [
"(map (fn (x) (* x x)) '(1 2 3)) ; => (1 4 9)\n(map + '(1 2 3) '(10 20 30)) ; => (11 22 33)"
],
"body": "Apply a function to each element of one or more lists.\n\n```sema\n(map (fn (x) (* x x)) '(1 2 3)) ; => (1 4 9)\n(map + '(1 2 3) '(10 20 30)) ; => (11 22 33)\n```"
},
{
"name": "mapcar",
"module": "lists",
"section": "Transformation",
"summary": "Apply `f` to each element of `seq`, returning a list of results. Alias of `map`.",
"params": [
{
"name": "f",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "list",
"examples": [
"(mapcar (fn (x) (* x x)) '(1 2 3)) ; => (1 4 9)"
],
"body": "Apply `f` to each element of `seq`, returning a list of results. Alias of `map`.\n\n```sema\n(mapcar (fn (x) (* x x)) '(1 2 3)) ; => (1 4 9)\n```"
},
{
"name": "member",
"module": "lists",
"section": "Searching",
"summary": "Return the tail of the list starting from the first matching element.",
"examples": [
"(member 3 '(1 2 3 4)) ; => (3 4)\n(member 9 '(1 2 3)) ; => #f"
],
"body": "Return the tail of the list starting from the first matching element.\n\n```sema\n(member 3 '(1 2 3 4)) ; => (3 4)\n(member 9 '(1 2 3)) ; => #f\n```"
},
{
"name": "nth",
"module": "lists",
"section": "Construction & Access",
"summary": "Return the element at index N (zero-based).",
"examples": [
"(nth '(10 20 30) 1) ; => 20\n(nth '(10 20 30) 0) ; => 10"
],
"body": "Return the element at index N (zero-based).\n\n```sema\n(nth '(10 20 30) 1) ; => 20\n(nth '(10 20 30) 0) ; => 10\n```"
},
{
"name": "partition",
"module": "lists",
"section": "Sublists",
"summary": "Split a list into two lists based on a predicate. Returns a list of two lists: elements that satisfy the predicate and those that don't.",
"examples": [
"(partition even? '(1 2 3 4 5)) ; => ((2 4) (1 3 5))"
],
"body": "Split a list into two lists based on a predicate. Returns a list of two lists: elements that satisfy the predicate and those that don't.\n\n```sema\n(partition even? '(1 2 3 4 5)) ; => ((2 4) (1 3 5))\n```"
},
{
"name": "range",
"module": "lists",
"section": "Basic Operations",
"summary": "Generate a list of integers. With one argument, generates 0 to N-1. With two, generates from start to end-1.",
"examples": [
"(range 5) ; => (0 1 2 3 4)\n(range 1 5) ; => (1 2 3 4)"
],
"body": "Generate a list of integers. With one argument, generates 0 to N-1. With two, generates from start to end-1.\n\n```sema\n(range 5) ; => (0 1 2 3 4)\n(range 1 5) ; => (1 2 3 4)\n```"
},
{
"name": "reduce",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Like `foldl` but uses the first element as the initial value.",
"examples": [
"(reduce + '(1 2 3 4 5)) ; => 15"
],
"body": "Like `foldl` but uses the first element as the initial value.\n\n```sema\n(reduce + '(1 2 3 4 5)) ; => 15\n```"
},
{
"name": "rest",
"module": "lists",
"section": "Construction & Access",
"summary": "Alias for `cdr`. Return the rest of the list.",
"examples": [
"(rest '(1 2 3)) ; => (2 3)"
],
"body": "Alias for `cdr`. Return the rest of the list.\n\n```sema\n(rest '(1 2 3)) ; => (2 3)\n```"
},
{
"name": "reverse",
"module": "lists",
"section": "Basic Operations",
"summary": "Reverse a list.",
"examples": [
"(reverse '(1 2 3)) ; => (3 2 1)"
],
"body": "Reverse a list.\n\n```sema\n(reverse '(1 2 3)) ; => (3 2 1)\n```"
},
{
"name": "some?",
"module": "lists",
"section": "Searching & Testing",
"summary": "Return `#t` if `pred` returns truthy for at least one element of `seq`. Alias of `any` (and of `any?`).",
"params": [
{
"name": "pred",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "bool",
"examples": [
"(some? even? '(1 3 4)) ; => #t\n(some? even? '(1 3 5)) ; => #f"
],
"body": "Return `#t` if `pred` returns truthy for at least one element of `seq`. Alias of `any` (and of `any?`).\n\n```sema\n(some? even? '(1 3 4)) ; => #t\n(some? even? '(1 3 5)) ; => #f\n```"
},
{
"name": "sort",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Sort a list in ascending order.",
"examples": [
"(sort '(3 1 4 1 5)) ; => (1 1 3 4 5)"
],
"body": "Sort a list in ascending order.\n\n```sema\n(sort '(3 1 4 1 5)) ; => (1 1 3 4 5)\n```"
},
{
"name": "sort-by",
"module": "lists",
"section": "Higher-Order Functions",
"summary": "Sort a list by a key function.",
"examples": [
"(sort-by length '(\"bb\" \"a\" \"ccc\")) ; => (\"a\" \"bb\" \"ccc\")\n(sort-by abs '(-3 1 -2)) ; => (1 -2 -3)"
],
"body": "Sort a list by a key function.\n\n```sema\n(sort-by length '(\"bb\" \"a\" \"ccc\")) ; => (\"a\" \"bb\" \"ccc\")\n(sort-by abs '(-3 1 -2)) ; => (1 -2 -3)\n```"
},
{
"name": "take",
"module": "lists",
"section": "Sublists",
"summary": "Take the first N elements.",
"examples": [
"(take 3 '(1 2 3 4 5)) ; => (1 2 3)\n(take 10 '(1 2)) ; => (1 2)"
],
"body": "Take the first N elements.\n\n```sema\n(take 3 '(1 2 3 4 5)) ; => (1 2 3)\n(take 10 '(1 2)) ; => (1 2)\n```"
},
{
"name": "take-while",
"module": "lists",
"section": "Slicing",
"summary": "Return the leading elements of `seq` for which `pred` is truthy, stopping at the first element that fails. Same as `list/take-while`.",
"params": [
{
"name": "pred",
"type": "function"
},
{
"name": "seq",
"type": "list | vector"
}
],
"returns": "list",
"examples": [
"(take-while (fn (x) (< x 4)) '(1 2 3 4 1)) ; => (1 2 3)"
],
"body": "Return the leading elements of `seq` for which `pred` is truthy, stopping at the first element that fails. Same as `list/take-while`.\n\n```sema\n(take-while (fn (x) (< x 4)) '(1 2 3 4 1)) ; => (1 2 3)\n```"
},
{
"name": "tap",
"module": "lists",
"section": "Utility",
"summary": "Apply a side-effect function to a value, then return the original value.",
"examples": [
"(tap 42 (fn (x) (println x))) ; prints 42, returns 42"
],
"body": "Apply a side-effect function to a value, then return the original value.\n\n```sema\n(tap 42 (fn (x) (println x))) ; prints 42, returns 42\n```"
},
{
"name": "zip",
"module": "lists",
"section": "Sublists",
"summary": "Combine corresponding elements from two lists into pairs.",
"examples": [
"(zip '(1 2 3) '(\"a\" \"b\" \"c\")) ; => ((1 \"a\") (2 \"b\") (3 \"c\"))"
],
"body": "Combine corresponding elements from two lists into pairs.\n\n```sema\n(zip '(1 2 3) '(\"a\" \"b\" \"c\")) ; => ((1 \"a\") (2 \"b\") (3 \"c\"))\n```"
},
{
"name": "llm/auto-configure",
"module": "llm",
"summary": "Auto-configure providers from environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `MOONSHOT_API_KEY`, `GOOGLE_API_KEY`, plus embedding keys like `JINA_API_KEY`, `VOYAGE_API_KEY`, `COHERE_API_KEY`). Ollama is always registered as a local fallback. Honors `SEMA_CHAT_PROVIDER`/`SEMA_CHAT_MODEL` and `SEMA_EMBEDDING_PROVIDER`/`SEMA_EMBEDDING_MODEL` overrides. Returns the default provider keyword, or nil if none configured.",
"returns": "keyword or nil",
"examples": [
"(llm/auto-configure) ; => :anthropic (if ANTHROPIC_API_KEY is set)"
],
"body": "Auto-configure providers from environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `MOONSHOT_API_KEY`, `GOOGLE_API_KEY`, plus embedding keys like `JINA_API_KEY`, `VOYAGE_API_KEY`, `COHERE_API_KEY`). Ollama is always registered as a local fallback. Honors `SEMA_CHAT_PROVIDER`/`SEMA_CHAT_MODEL` and `SEMA_EMBEDDING_PROVIDER`/`SEMA_EMBEDDING_MODEL` overrides. Returns the default provider keyword, or nil if none configured.\n\n```sema\n(llm/auto-configure) ; => :anthropic (if ANTHROPIC_API_KEY is set)\n```"
},
{
"name": "llm/batch",
"module": "llm",
"summary": "Send a list of prompt strings to the default provider in parallel (via the provider's batch completion) and return a list of completion strings in the same order. The opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`.",
"params": [
{
"name": "prompts"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "list",
"examples": [
"(llm/batch [\"Capital of France?\" \"Capital of Japan?\"] {:max-tokens 20})"
],
"body": "Send a list of prompt strings to the default provider in parallel (via the provider's batch completion) and return a list of completion strings in the same order. The opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`.\n\n```sema\n(llm/batch [\"Capital of France?\" \"Capital of Japan?\"] {:max-tokens 20})\n```"
},
{
"name": "llm/budget-remaining",
"module": "llm",
"summary": "Return the current budget status, or nil if no budget is set. When a cost budget is active the map includes `:limit`, `:spent`, and `:remaining` (USD); when a token budget is active it includes `:token-limit`, `:tokens-spent`, and `:tokens-remaining`.",
"returns": "map or nil",
"examples": [
"(llm/set-budget 1.0)\n(llm/budget-remaining) ; => {:limit 1.0 :spent 0.0 :remaining 1.0}"
],
"body": "Return the current budget status, or nil if no budget is set. When a cost budget is active the map includes `:limit`, `:spent`, and `:remaining` (USD); when a token budget is active it includes `:token-limit`, `:tokens-spent`, and `:tokens-remaining`.\n\n```sema\n(llm/set-budget 1.0)\n(llm/budget-remaining) ; => {:limit 1.0 :spent 0.0 :remaining 1.0}\n```"
},
{
"name": "llm/cache-clear",
"module": "llm",
"summary": "Clear the LLM response cache: empties the in-memory cache, deletes cached `.json` files from the on-disk cache directory, and resets hit/miss counters. Returns the number of in-memory entries that were removed.",
"returns": "int",
"examples": [
"(llm/cache-clear) ; => 12"
],
"body": "Clear the LLM response cache: empties the in-memory cache, deletes cached `.json` files from the on-disk cache directory, and resets hit/miss counters. Returns the number of in-memory entries that were removed.\n\n```sema\n(llm/cache-clear) ; => 12\n```"
},
{
"name": "llm/cache-key",
"module": "llm",
"summary": "Compute the cache key string that would be used for a given prompt and options. The opts map accepts `:model`, `:temperature`, and `:system`. Useful for inspecting or pre-seeding the response cache.",
"params": [
{
"name": "prompt",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/cache-key \"hello\" {:model \"gpt-4o-mini\"}) ; => \"a1b2c3...\""
],
"body": "Compute the cache key string that would be used for a given prompt and options. The opts map accepts `:model`, `:temperature`, and `:system`. Useful for inspecting or pre-seeding the response cache.\n\n```sema\n(llm/cache-key \"hello\" {:model \"gpt-4o-mini\"}) ; => \"a1b2c3...\"\n```"
},
{
"name": "llm/cache-stats",
"module": "llm",
"summary": "Return cache statistics as a map with `:hits`, `:misses`, and `:size` (the number of entries currently in the in-memory cache).",
"returns": "map",
"examples": [
"(llm/cache-stats) ; => {:hits 3 :misses 1 :size 4}"
],
"body": "Return cache statistics as a map with `:hits`, `:misses`, and `:size` (the number of entries currently in the in-memory cache).\n\n```sema\n(llm/cache-stats) ; => {:hits 3 :misses 1 :size 4}\n```"
},
{
"name": "llm/chat",
"module": "llm",
"summary": "Run a multi-message chat against the default provider and return the assistant's text reply. The first argument is a sequence of messages. The optional opts map accepts `:model`, `:max-tokens`, `:temperature`, `:system`, `:tools`, `:tool-mode` (`:auto` or `:none`), and `:max-tool-rounds` (default 10). When tools are supplied and tool-mode is not `:none`, it runs a tool-execution loop.",
"params": [
{
"name": "messages"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/chat (list (message :user \"What is the capital of France?\"))\n {:model \"gpt-4o-mini\"})"
],
"body": "Run a multi-message chat against the default provider and return the assistant's text reply. The first argument is a sequence of messages. The optional opts map accepts `:model`, `:max-tokens`, `:temperature`, `:system`, `:tools`, `:tool-mode` (`:auto` or `:none`), and `:max-tool-rounds` (default 10). When tools are supplied and tool-mode is not `:none`, it runs a tool-execution loop.\n\n```sema\n(llm/chat (list (message :user \"What is the capital of France?\"))\n {:model \"gpt-4o-mini\"})\n```"
},
{
"name": "llm/classify",
"module": "llm",
"summary": "Classify text into exactly one of the given categories. The first argument is a sequence of category names (keywords or strings); the model is told to respond with only the matching category name. Returns a keyword if the matched category was supplied as a keyword, otherwise a string. The opts map accepts `:model`.",
"params": [
{
"name": "categories"
},
{
"name": "text",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "keyword or string",
"examples": [
"(llm/classify [:positive :negative :neutral] \"I absolutely love this product!\")\n; => :positive"
],
"body": "Classify text into exactly one of the given categories. The first argument is a sequence of category names (keywords or strings); the model is told to respond with only the matching category name. Returns a keyword if the matched category was supplied as a keyword, otherwise a string. The opts map accepts `:model`.\n\n```sema\n(llm/classify [:positive :negative :neutral] \"I absolutely love this product!\")\n; => :positive\n```"
},
{
"name": "llm/clear-budget",
"module": "llm",
"summary": "Clear any active budget limit, removing both the cost and token budget caps.",
"returns": "nil",
"examples": [
"(llm/clear-budget)"
],
"body": "Clear any active budget limit, removing both the cost and token budget caps.\n\n```sema\n(llm/clear-budget)\n```"
},
{
"name": "llm/compare",
"module": "llm",
"summary": "Compare two texts using the default provider and return a parsed JSON map with `:similarity` (0.0–1.0), `:differences` (a list of key differences), and `:summary` (a brief comparison). The opts map accepts `:model`.",
"params": [
{
"name": "text-a",
"type": "string"
},
{
"name": "text-b",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "map",
"examples": [
"(llm/compare \"The cat sat on the mat\" \"A cat is sitting on a rug\")"
],
"body": "Compare two texts using the default provider and return a parsed JSON map with `:similarity` (0.0–1.0), `:differences` (a list of key differences), and `:summary` (a brief comparison). The opts map accepts `:model`.\n\n```sema\n(llm/compare \"The cat sat on the mat\" \"A cat is sitting on a rug\")\n```"
},
{
"name": "llm/complete",
"module": "llm",
"summary": "Send a single prompt to the default provider and return the completion text. The first argument is a prompt string (or a prompt value). The optional opts map accepts `:model`, `:max-tokens` (defaults to 4096), `:temperature`, and `:system`. Tracks token usage.",
"params": [
{
"name": "prompt",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/complete \"Write a haiku about autumn\" {:max-tokens 100 :temperature 0.7})"
],
"body": "Send a single prompt to the default provider and return the completion text. The first argument is a prompt string (or a prompt value). The optional opts map accepts `:model`, `:max-tokens` (defaults to 4096), `:temperature`, and `:system`. Tracks token usage.\n\n```sema\n(llm/complete \"Write a haiku about autumn\" {:max-tokens 100 :temperature 0.7})\n```"
},
{
"name": "llm/configure",
"module": "llm",
"summary": "Configure and register an LLM provider. Chat providers (`:anthropic`, `:openai`, `:gemini`, `:groq`, `:xai`, `:mistral`, `:moonshot`, `:ollama`, or any unknown name treated as OpenAI-compatible) become the default chat provider. Embedding providers (`:jina`, `:voyage`, `:cohere`) are registered as the embedding provider instead — equivalent to `llm/configure-embeddings`. The map carries `:api-key`, optional `:default-model`, and `:base-url`/`:host` depending on the provider. Returns nil.",
"params": [
{
"name": "provider",
"type": "keyword"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "nil",
"examples": [
"(llm/configure :anthropic {:api-key \"sk-ant-...\" :default-model \"claude-sonnet-4-6\"})"
],
"body": "Configure and register an LLM provider. Chat providers (`:anthropic`, `:openai`, `:gemini`, `:groq`, `:xai`, `:mistral`, `:moonshot`, `:ollama`, or any unknown name treated as OpenAI-compatible) become the default chat provider. Embedding providers (`:jina`, `:voyage`, `:cohere`) are registered as the embedding provider instead — equivalent to `llm/configure-embeddings`. The map carries `:api-key`, optional `:default-model`, and `:base-url`/`:host` depending on the provider. Returns nil.\n\n```sema\n(llm/configure :anthropic {:api-key \"sk-ant-...\" :default-model \"claude-sonnet-4-6\"})\n```"
},
{
"name": "llm/configure-embeddings",
"module": "llm",
"summary": "Configure and register the embedding provider used by `llm/embed`. The provider keyword may be `:jina`, `:voyage`, `:cohere`, or any other (treated as OpenAI-compatible). The map carries `:api-key`, optional `:default-model`/`:model`, and `:base-url` for OpenAI-compatible endpoints. Returns nil.",
"params": [
{
"name": "provider",
"type": "keyword"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "nil",
"examples": [
"(llm/configure-embeddings :openai {:api-key \"sk-...\" :model \"text-embedding-3-small\"})"
],
"body": "Configure and register the embedding provider used by `llm/embed`. The provider keyword may be `:jina`, `:voyage`, `:cohere`, or any other (treated as OpenAI-compatible). The map carries `:api-key`, optional `:default-model`/`:model`, and `:base-url` for OpenAI-compatible endpoints. Returns nil.\n\n```sema\n(llm/configure-embeddings :openai {:api-key \"sk-...\" :model \"text-embedding-3-small\"})\n```"
},
{
"name": "llm/current-provider",
"module": "llm",
"summary": "Return information about the active default provider as a map with `:name` (keyword) and `:model` (the provider's default model string). Returns nil if no provider is configured.",
"returns": "map or nil",
"examples": [
"(llm/current-provider) ; => {:name :anthropic :model \"claude-sonnet-4-6\"}"
],
"body": "Return information about the active default provider as a map with `:name` (keyword) and `:model` (the provider's default model string). Returns nil if no provider is configured.\n\n```sema\n(llm/current-provider) ; => {:name :anthropic :model \"claude-sonnet-4-6\"}\n```"
},
{
"name": "llm/default-provider",
"module": "llm",
"summary": "Return the name of the current default provider as a keyword, or nil if none is configured.",
"returns": "keyword or nil",
"examples": [
"(llm/default-provider) ; => :anthropic"
],
"body": "Return the name of the current default provider as a keyword, or nil if none is configured.\n\n```sema\n(llm/default-provider) ; => :anthropic\n```"
},
{
"name": "llm/define-provider",
"module": "llm",
"summary": "Define a custom LLM provider implemented in Sema. The map must include a `:complete` function (lambda or native fn) that receives a request map and returns a string or a response map (with `:content`, optional `:role`, `:model`, `:usage`, `:tool-calls`); `:default-model` is optional. Registers the provider and makes it the default, returning its name keyword.",
"params": [
{
"name": "name",
"type": "keyword"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "keyword",
"examples": [
"(llm/define-provider :echo\n {:complete (fn [req] {:content (string-append \"echo: \" (:content (first (:messages req))))})\n :default-model \"echo-1\"})"
],
"body": "Define a custom LLM provider implemented in Sema. The map must include a `:complete` function (lambda or native fn) that receives a request map and returns a string or a response map (with `:content`, optional `:role`, `:model`, `:usage`, `:tool-calls`); `:default-model` is optional. Registers the provider and makes it the default, returning its name keyword.\n\n```sema\n(llm/define-provider :echo\n {:complete (fn [req] {:content (string-append \"echo: \" (:content (first (:messages req))))})\n :default-model \"echo-1\"})\n```"
},
{
"name": "llm/embed",
"module": "llm",
"summary": "Compute embeddings using the configured embedding provider. Given a single string, returns one embedding as a bytevector (f64 little-endian floats); given a list of strings, returns a list of bytevectors. The opts map accepts `:model`. Use the `embedding/*` helpers to convert to/from float lists.",
"params": [
{
"name": "input"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "bytevector or list",
"examples": [
"(llm/embed \"the quick brown fox\") ; => #bytevector(...)\n(llm/embed [\"alpha\" \"beta\"] {:model \"text-embedding-3-small\"})"
],
"body": "Compute embeddings using the configured embedding provider. Given a single string, returns one embedding as a bytevector (f64 little-endian floats); given a list of strings, returns a list of bytevectors. The opts map accepts `:model`. Use the `embedding/*` helpers to convert to/from float lists.\n\n```sema\n(llm/embed \"the quick brown fox\") ; => #bytevector(...)\n(llm/embed [\"alpha\" \"beta\"] {:model \"text-embedding-3-small\"})\n```"
},
{
"name": "llm/extract",
"module": "llm",
"summary": "Extract structured data from text into a value matching the given schema. The model is instructed to return JSON-only; the response is parsed and (by default) validated against the schema, with automatic retries. The opts map accepts `:model`, `:validate` (default true), `:retries` (default 2), and `:reask?` (default true, sends the prior response and validation error back to the model). Returns the extracted value.",
"params": [
{
"name": "schema"
},
{
"name": "text",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "map",
"examples": [
"(llm/extract {:name :string :age :int} \"John Doe is 42 years old\" {:retries 1})"
],
"body": "Extract structured data from text into a value matching the given schema. The model is instructed to return JSON-only; the response is parsed and (by default) validated against the schema, with automatic retries. The opts map accepts `:model`, `:validate` (default true), `:retries` (default 2), and `:reask?` (default true, sends the prior response and validation error back to the model). Returns the extracted value.\n\n```sema\n(llm/extract {:name :string :age :int} \"John Doe is 42 years old\" {:retries 1})\n```"
},
{
"name": "llm/extract-from-image",
"module": "llm",
"summary": "Extract structured data from an image into a value matching the given schema. The source is either a file-path string or a bytevector of image bytes; the media type is auto-detected. The model is asked to return JSON only, which is parsed into a Sema value. The opts map accepts `:model`.",
"params": [
{
"name": "schema"
},
{
"name": "source"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "map",
"examples": [
"(llm/extract-from-image {:total :number :date :string} \"receipt.png\")"
],
"body": "Extract structured data from an image into a value matching the given schema. The source is either a file-path string or a bytevector of image bytes; the media type is auto-detected. The model is asked to return JSON only, which is parsed into a Sema value. The opts map accepts `:model`.\n\n```sema\n(llm/extract-from-image {:total :number :date :string} \"receipt.png\")\n```"
},
{
"name": "llm/last-usage",
"module": "llm",
"summary": "Return token usage for the most recent LLM call as a map with `:prompt-tokens`, `:completion-tokens`, `:total-tokens`, `:model`, and (when pricing is known) `:cost-usd`. Returns nil if no call has been made yet.",
"returns": "map or nil",
"examples": [
"(llm/complete \"hi\")\n(llm/last-usage) ; => {:prompt-tokens 8 :completion-tokens 12 :total-tokens 20 :model \"...\"}"
],
"body": "Return token usage for the most recent LLM call as a map with `:prompt-tokens`, `:completion-tokens`, `:total-tokens`, `:model`, and (when pricing is known) `:cost-usd`. Returns nil if no call has been made yet.\n\n```sema\n(llm/complete \"hi\")\n(llm/last-usage) ; => {:prompt-tokens 8 :completion-tokens 12 :total-tokens 20 :model \"...\"}\n```"
},
{
"name": "llm/list-providers",
"module": "llm",
"summary": "Return a list of keywords naming all currently configured providers.",
"returns": "list",
"examples": [
"(llm/list-providers) ; => (:anthropic :ollama)"
],
"body": "Return a list of keywords naming all currently configured providers.\n\n```sema\n(llm/list-providers) ; => (:anthropic :ollama)\n```"
},
{
"name": "llm/pmap",
"module": "llm",
"summary": "Map a function over a collection to produce prompt strings, then send all prompts to the default provider in parallel (via the provider's batch completion). Each item is passed to `fn` to build its prompt; the results are returned as a list of completion strings in input order. The opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`.",
"params": [
{
"name": "fn"
},
{
"name": "collection"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "list",
"examples": [
"(llm/pmap (fn [topic] (string-append \"Define: \" topic))\n [\"entropy\" \"recursion\" \"monad\"]\n {:max-tokens 60})"
],
"body": "Map a function over a collection to produce prompt strings, then send all prompts to the default provider in parallel (via the provider's batch completion). Each item is passed to `fn` to build its prompt; the results are returned as a list of completion strings in input order. The opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`.\n\n```sema\n(llm/pmap (fn [topic] (string-append \"Define: \" topic))\n [\"entropy\" \"recursion\" \"monad\"]\n {:max-tokens 60})\n```"
},
{
"name": "llm/pricing-status",
"module": "llm",
"summary": "Return the status of the pricing table used for cost estimates: a map with `:source` (a symbol indicating where pricing came from) and, when available, `:updated-at` (a date string).",
"returns": "map",
"examples": [
"(llm/pricing-status) ; => {:source hardcoded}"
],
"body": "Return the status of the pricing table used for cost estimates: a map with `:source` (a symbol indicating where pricing came from) and, when available, `:updated-at` (a date string).\n\n```sema\n(llm/pricing-status) ; => {:source hardcoded}\n```"
},
{
"name": "llm/providers",
"module": "llm",
"summary": "Return a list of keywords naming all currently configured providers. Equivalent to `llm/list-providers`.",
"returns": "list",
"examples": [
"(llm/providers) ; => (:anthropic :ollama)"
],
"body": "Return a list of keywords naming all currently configured providers. Equivalent to `llm/list-providers`.\n\n```sema\n(llm/providers) ; => (:anthropic :ollama)\n```"
},
{
"name": "llm/reset-usage",
"module": "llm",
"summary": "Reset session usage tracking: zeros the accumulated prompt/completion token counts, clears the last-usage record, and resets session cost to zero.",
"returns": "nil",
"examples": [
"(llm/reset-usage)"
],
"body": "Reset session usage tracking: zeros the accumulated prompt/completion token counts, clears the last-usage record, and resets session cost to zero.\n\n```sema\n(llm/reset-usage)\n```"
},
{
"name": "llm/send",
"module": "llm",
"summary": "Send a structured prompt value (built with the prompt helpers) to the default provider and return the completion. The first argument must be a prompt value; the optional opts map carries model/generation options.",
"params": [
{
"name": "prompt",
"type": "prompt"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/send (prompt (system \"You are a helpful assistant.\")\n (user \"Summarize the plot of Hamlet in one line.\"))\n {:max-tokens 80})"
],
"body": "Send a structured prompt value (built with the prompt helpers) to the default provider and return the completion. The first argument must be a prompt value; the optional opts map carries model/generation options.\n\n```sema\n(llm/send (prompt (system \"You are a helpful assistant.\")\n (user \"Summarize the plot of Hamlet in one line.\"))\n {:max-tokens 80})\n```"
},
{
"name": "llm/session-usage",
"module": "llm",
"summary": "Return cumulative token usage for the whole session as a map with `:prompt-tokens`, `:completion-tokens`, `:total-tokens`, and `:cost-usd`. The totals accumulate across all LLM calls until reset with `llm/reset-usage`.",
"returns": "map",
"examples": [
"(llm/session-usage) ; => {:prompt-tokens 120 :completion-tokens 340 :total-tokens 460 :cost-usd 0.0021}"
],
"body": "Return cumulative token usage for the whole session as a map with `:prompt-tokens`, `:completion-tokens`, `:total-tokens`, and `:cost-usd`. The totals accumulate across all LLM calls until reset with `llm/reset-usage`.\n\n```sema\n(llm/session-usage) ; => {:prompt-tokens 120 :completion-tokens 340 :total-tokens 460 :cost-usd 0.0021}\n```"
},
{
"name": "llm/set-budget",
"module": "llm",
"summary": "Set a global spending budget (in USD) for LLM calls and reset the amount spent to zero. Subsequent calls accumulate cost; exceeding the limit raises an error. Costs are estimated from the pricing table, so enforcement is best-effort when pricing is unknown.",
"params": [
{
"name": "max-cost-usd",
"type": "number"
}
],
"returns": "nil",
"examples": [
"(llm/set-budget 0.50)"
],
"body": "Set a global spending budget (in USD) for LLM calls and reset the amount spent to zero. Subsequent calls accumulate cost; exceeding the limit raises an error. Costs are estimated from the pricing table, so enforcement is best-effort when pricing is unknown.\n\n```sema\n(llm/set-budget 0.50)\n```"
},
{
"name": "llm/set-default",
"module": "llm",
"summary": "Set which already-configured provider is the default for chat calls. The argument is a provider keyword or string. Errors if the named provider is not configured. Returns the provider name as a keyword.",
"params": [
{
"name": "provider"
}
],
"returns": "keyword",
"examples": [
"(llm/set-default :openai)"
],
"body": "Set which already-configured provider is the default for chat calls. The argument is a provider keyword or string. Errors if the named provider is not configured. Returns the provider name as a keyword.\n\n```sema\n(llm/set-default :openai)\n```"
},
{
"name": "llm/set-pricing",
"module": "llm",
"summary": "Register custom pricing for cost calculation. The model pattern matches model names; the two numbers are the input and output costs per million tokens (USD). Used to compute `:cost-usd` in usage reports.",
"params": [
{
"name": "model-pattern",
"type": "string"
},
{
"name": "input-per-million",
"type": "number"
},
{
"name": "output-per-million",
"type": "number"
}
],
"returns": "nil",
"examples": [
"(llm/set-pricing \"my-model\" 0.5 1.5)"
],
"body": "Register custom pricing for cost calculation. The model pattern matches model names; the two numbers are the input and output costs per million tokens (USD). Used to compute `:cost-usd` in usage reports.\n\n```sema\n(llm/set-pricing \"my-model\" 0.5 1.5)\n```"
},
{
"name": "llm/similarity",
"module": "llm",
"summary": "Compute the cosine similarity between two embeddings. Both arguments must be the same type: either two embedding bytevectors (f64 little-endian, length a multiple of 8) or two lists of numbers of equal length. Returns a float; 0.0 when either vector has zero magnitude.",
"params": [
{
"name": "a"
},
{
"name": "b"
}
],
"returns": "float",
"examples": [
"(llm/similarity (llm/embed \"cat\") (llm/embed \"kitten\")) ; => ~0.89"
],
"body": "Compute the cosine similarity between two embeddings. Both arguments must be the same type: either two embedding bytevectors (f64 little-endian, length a multiple of 8) or two lists of numbers of equal length. Returns a float; 0.0 when either vector has zero magnitude.\n\n```sema\n(llm/similarity (llm/embed \"cat\") (llm/embed \"kitten\")) ; => ~0.89\n```"
},
{
"name": "llm/stream",
"module": "llm",
"summary": "Stream a completion from the default provider. The first argument is a prompt string, a prompt value, or a messages sequence. An optional function argument is invoked with each text chunk; without a callback, chunks are printed to stdout. An optional opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`. Returns the full accumulated response string.",
"params": [
{
"name": "prompt"
},
{
"name": "callback"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/stream \"Tell me a short story\" (fn [chunk] (display chunk)) {:max-tokens 200})"
],
"body": "Stream a completion from the default provider. The first argument is a prompt string, a prompt value, or a messages sequence. An optional function argument is invoked with each text chunk; without a callback, chunks are printed to stdout. An optional opts map accepts `:model`, `:max-tokens`, `:temperature`, and `:system`. Returns the full accumulated response string.\n\n```sema\n(llm/stream \"Tell me a short story\" (fn [chunk] (display chunk)) {:max-tokens 200})\n```"
},
{
"name": "llm/summarize",
"module": "llm",
"summary": "Summarize a block of text using the default provider. The opts map accepts `:model`, `:max-length` (target word count), and `:style` (`\"paragraph\"` default, `\"bullet-points\"`/`\"bullets\"`, or `\"one-line\"`). Returns the summary string.",
"params": [
{
"name": "text",
"type": "string"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "string",
"examples": [
"(llm/summarize long-article {:style \"bullet-points\" :max-length 100})"
],
"body": "Summarize a block of text using the default provider. The opts map accepts `:model`, `:max-length` (target word count), and `:style` (`\"paragraph\"` default, `\"bullet-points\"`/`\"bullets\"`, or `\"one-line\"`). Returns the summary string.\n\n```sema\n(llm/summarize long-article {:style \"bullet-points\" :max-length 100})\n```"
},
{
"name": "llm/token-count",
"module": "llm",
"summary": "Estimate the number of tokens in a string (or the combined length of a list of strings) using a simple chars-divided-by-4 heuristic. Returns an integer token estimate.",
"params": [
{
"name": "input"
}
],
"returns": "int",
"examples": [
"(llm/token-count \"the quick brown fox jumps\") ; => 6"
],
"body": "Estimate the number of tokens in a string (or the combined length of a list of strings) using a simple chars-divided-by-4 heuristic. Returns an integer token estimate.\n\n```sema\n(llm/token-count \"the quick brown fox jumps\") ; => 6\n```"
},
{
"name": "llm/token-estimate",
"module": "llm",
"summary": "Estimate tokens for a string and return details as a map with `:tokens` (the chars/4 estimate), `:method` (`\"chars/4\"`), and `:chars` (the character count).",
"params": [
{
"name": "input"
}
],
"returns": "map",
"examples": [
"(llm/token-estimate \"hello world\") ; => {:tokens 2 :method \"chars/4\" :chars 11}"
],
"body": "Estimate tokens for a string and return details as a map with `:tokens` (the chars/4 estimate), `:method` (`\"chars/4\"`), and `:chars` (the character count).\n\n```sema\n(llm/token-estimate \"hello world\") ; => {:tokens 2 :method \"chars/4\" :chars 11}\n```"
},
{
"name": "llm/with-budget",
"module": "llm",
"summary": "Run a zero-argument function under a scoped budget. The opts map requires at least `:max-cost-usd` and/or `:max-tokens`; the scope is pushed before calling the thunk and restored afterward (even on error). LLM calls inside the thunk count against the scoped limit. Returns the thunk's result.",
"params": [
{
"name": "opts",
"type": "map"
},
{
"name": "thunk"
}
],
"returns": "any",
"examples": [
"(llm/with-budget {:max-cost-usd 0.10}\n (fn [] (llm/complete \"summarize this in one line\")))"
],
"body": "Run a zero-argument function under a scoped budget. The opts map requires at least `:max-cost-usd` and/or `:max-tokens`; the scope is pushed before calling the thunk and restored afterward (even on error). LLM calls inside the thunk count against the scoped limit. Returns the thunk's result.\n\n```sema\n(llm/with-budget {:max-cost-usd 0.10}\n (fn [] (llm/complete \"summarize this in one line\")))\n```"
},
{
"name": "llm/with-cache",
"module": "llm",
"summary": "Run a zero-argument function with LLM response caching enabled for its duration. With two arguments, the first is an opts map accepting `:ttl` (cache time-to-live in seconds, default 3600); with one argument it is just the thunk. The previous cache settings are restored after the call. Returns the thunk's result.",
"params": [
{
"name": "opts",
"type": "map"
},
{
"name": "thunk"
}
],
"returns": "any",
"examples": [
"(llm/with-cache {:ttl 600}\n (fn [] (llm/complete \"what is 2+2?\")))"
],
"body": "Run a zero-argument function with LLM response caching enabled for its duration. With two arguments, the first is an opts map accepting `:ttl` (cache time-to-live in seconds, default 3600); with one argument it is just the thunk. The previous cache settings are restored after the call. Returns the thunk's result.\n\n```sema\n(llm/with-cache {:ttl 600}\n (fn [] (llm/complete \"what is 2+2?\")))\n```"
},
{
"name": "llm/with-fallback",
"module": "llm",
"summary": "Run a zero-argument function with a fallback provider chain in effect. The first argument is a list of provider keywords/strings to try in order if the primary provider fails. The previous fallback chain is restored after the call. Returns the thunk's result.",
"params": [
{
"name": "providers"
},
{
"name": "thunk"
}
],
"returns": "any",
"examples": [
"(llm/with-fallback [:anthropic :openai :ollama]\n (fn [] (llm/complete \"draft a one-line tagline\")))"
],
"body": "Run a zero-argument function with a fallback provider chain in effect. The first argument is a list of provider keywords/strings to try in order if the primary provider fails. The previous fallback chain is restored after the call. Returns the thunk's result.\n\n```sema\n(llm/with-fallback [:anthropic :openai :ollama]\n (fn [] (llm/complete \"draft a one-line tagline\")))\n```"
},
{
"name": "llm/with-rate-limit",
"module": "llm",
"summary": "Run a zero-argument function with a requests-per-second rate limit in effect for LLM calls. The first argument is the allowed rate (requests per second). The previous rate-limit setting is restored after the call. Returns the thunk's result.",
"params": [
{
"name": "rps",
"type": "number"
},
{
"name": "thunk"
}
],
"returns": "any",
"examples": [
"(llm/with-rate-limit 2\n (fn [] (llm/batch [\"q1\" \"q2\" \"q3\"])))"
],
"body": "Run a zero-argument function with a requests-per-second rate limit in effect for LLM calls. The first argument is the allowed rate (requests per second). The previous rate-limit setting is restored after the call. Returns the thunk's result.\n\n```sema\n(llm/with-rate-limit 2\n (fn [] (llm/batch [\"q1\" \"q2\" \"q3\"])))\n```"
},
{
"name": "log/debug",
"module": "log",
"summary": "Write a `[DEBUG]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.",
"params": [
{
"name": "args"
}
],
"returns": "nil",
"examples": [
"(log/debug \"request payload\" payload)"
],
"body": "Write a `[DEBUG]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.\n\n```sema\n(log/debug \"request payload\" payload)\n```"
},
{
"name": "log/error",
"module": "log",
"summary": "Write an `[ERROR]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.",
"params": [
{
"name": "args"
}
],
"returns": "nil",
"examples": [
"(log/error \"failed to connect:\" err)"
],
"body": "Write an `[ERROR]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.\n\n```sema\n(log/error \"failed to connect:\" err)\n```"
},
{
"name": "log/info",
"module": "log",
"summary": "Write an `[INFO]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.",
"params": [
{
"name": "args"
}
],
"returns": "nil",
"examples": [
"(log/info \"server started on port\" 8080)"
],
"body": "Write an `[INFO]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.\n\n```sema\n(log/info \"server started on port\" 8080)\n```"
},
{
"name": "log/warn",
"module": "log",
"summary": "Write a `[WARN]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.",
"params": [
{
"name": "args"
}
],
"returns": "nil",
"examples": [
"(log/warn \"retrying request, attempt\" n)"
],
"body": "Write a `[WARN]` log line to stderr. Accepts one or more values, joined by spaces (strings as-is, others stringified). Any active logging context is appended.\n\n```sema\n(log/warn \"retrying request, attempt\" n)\n```"
},
{
"name": "assoc",
"module": "maps",
"section": "Maps",
"summary": "Add or update a key-value pair, returning a new map.",
"examples": [
"(assoc {:a 1} :b 2) ; => {:a 1 :b 2}\n(assoc {:a 1} :a 99) ; => {:a 99}"
],
"body": "Add or update a key-value pair, returning a new map.\n\n```sema\n(assoc {:a 1} :b 2) ; => {:a 1 :b 2}\n(assoc {:a 1} :a 99) ; => {:a 99}\n```"
},
{
"name": "assoc-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Return a copy of `m` with `value` set at the nested key `path`, creating intermediate maps as needed. Same as `map/assoc-in`.",
"params": [
{
"name": "m",
"type": "map"
},
{
"name": "path",
"type": "list | vector"
},
{
"name": "value",
"type": "any"
}
],
"returns": "map",
"examples": [
"(assoc-in {:a {:b 1}} [:a :c] 2) ; => {:a {:b 1 :c 2}}\n(assoc-in {} [:x :y] 9) ; => {:x {:y 9}}"
],
"body": "Return a copy of `m` with `value` set at the nested key `path`, creating intermediate maps as needed. Same as `map/assoc-in`.\n\n```sema\n(assoc-in {:a {:b 1}} [:a :c] 2) ; => {:a {:b 1 :c 2}}\n(assoc-in {} [:x :y] 9) ; => {:x {:y 9}}\n```"
},
{
"name": "contains?",
"module": "maps",
"section": "Maps",
"summary": "Test if a map contains a key.",
"examples": [
"(contains? {:a 1} :a) ; => #t\n(contains? {:a 1} :b) ; => #f"
],
"body": "Test if a map contains a key.\n\n```sema\n(contains? {:a 1} :a) ; => #t\n(contains? {:a 1} :b) ; => #f\n```"
},
{
"name": "count",
"module": "maps",
"section": "Maps",
"summary": "Return the number of key-value pairs.",
"examples": [
"(count {:a 1 :b 2}) ; => 2"
],
"body": "Return the number of key-value pairs.\n\n```sema\n(count {:a 1 :b 2}) ; => 2\n```"
},
{
"name": "deep-merge",
"module": "maps",
"section": "Combining",
"summary": "Recursively merge maps left to right. Nested maps are merged; non-map values from later arguments overwrite earlier ones. Same as `map/deep-merge`.",
"returns": "map",
"examples": [
"(deep-merge {:a {:x 1}} {:a {:y 2}}) ; => {:a {:x 1 :y 2}}\n(deep-merge {:a 1} {:a 2}) ; => {:a 2}"
],
"body": "Recursively merge maps left to right. Nested maps are merged; non-map values from later arguments overwrite earlier ones. Same as `map/deep-merge`.\n\n```sema\n(deep-merge {:a {:x 1}} {:a {:y 2}}) ; => {:a {:x 1 :y 2}}\n(deep-merge {:a 1} {:a 2}) ; => {:a 2}\n```"
},
{
"name": "dissoc",
"module": "maps",
"section": "Maps",
"summary": "Remove a key, returning a new map. Works on both maps and hashmaps.",
"examples": [
"(dissoc {:a 1 :b 2} :a) ; => {:b 2}\n(dissoc (hashmap/new :a 1 :b 2) :a) ; hashmap without :a"
],
"body": "Remove a key, returning a new map. Works on both maps and hashmaps.\n\n```sema\n(dissoc {:a 1 :b 2} :a) ; => {:b 2}\n(dissoc (hashmap/new :a 1 :b 2) :a) ; hashmap without :a\n```"
},
{
"name": "get",
"module": "maps",
"section": "Maps",
"summary": "Look up a value by key. Works on both maps and hashmaps.",
"examples": [
"(get {:a 1 :b 2} :a) ; => 1\n(get {:a 1 :b 2} :z) ; => nil"
],
"body": "Look up a value by key. Works on both maps and hashmaps.\n\n```sema\n(get {:a 1 :b 2} :a) ; => 1\n(get {:a 1 :b 2} :z) ; => nil\n```"
},
{
"name": "get-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Access a value at a nested key path. Returns `nil` (or `default`) if any key along the path is missing. Same as `map/get-in`.",
"params": [
{
"name": "m",
"type": "map"
},
{
"name": "path",
"type": "list | vector"
},
{
"name": "default",
"type": "any"
}
],
"examples": [
"(get-in {:a {:b {:c 42}}} [:a :b :c]) ; => 42\n(get-in {:a {:b 1}} [:a :c]) ; => nil\n(get-in {:a {:b 1}} [:a :c] \"default\") ; => \"default\""
],
"body": "Access a value at a nested key path. Returns `nil` (or `default`) if any key along the path is missing. Same as `map/get-in`.\n\n```sema\n(get-in {:a {:b {:c 42}}} [:a :b :c]) ; => 42\n(get-in {:a {:b 1}} [:a :c]) ; => nil\n(get-in {:a {:b 1}} [:a :c] \"default\") ; => \"default\"\n```"
},
{
"name": "hash-map",
"module": "maps",
"section": "Construction",
"summary": "Construct a map from alternating key/value arguments. Requires an even number of arguments. Alias of `map/new`.",
"returns": "map",
"examples": [
"(hash-map :a 1 :b 2) ; => {:a 1 :b 2}\n(hash-map) ; => {}"
],
"body": "Construct a map from alternating key/value arguments. Requires an even number of arguments. Alias of `map/new`.\n\n```sema\n(hash-map :a 1 :b 2) ; => {:a 1 :b 2}\n(hash-map) ; => {}\n```"
},
{
"name": "hash-map?",
"module": "maps",
"section": "Predicates",
"summary": "Return `#t` if `x` is a map. Alias of `map?`.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "bool",
"examples": [
"(hash-map? {:a 1}) ; => #t\n(hash-map? '(1 2)) ; => #f"
],
"body": "Return `#t` if `x` is a map. Alias of `map?`.\n\n```sema\n(hash-map? {:a 1}) ; => #t\n(hash-map? '(1 2)) ; => #f\n```"
},
{
"name": "hash-ref",
"module": "maps",
"section": "Access",
"summary": "Look up `key` in map `m`, returning the associated value or `default` (`nil` if omitted) when the key is missing. Alias of `get`.",
"params": [
{
"name": "m",
"type": "map"
},
{
"name": "key",
"type": "any"
},
{
"name": "default",
"type": "any"
}
],
"examples": [
"(hash-ref {:a 1 :b 2} :a) ; => 1\n(hash-ref {:a 1} :missing) ; => nil\n(hash-ref {:a 1} :missing \"none\") ; => \"none\""
],
"body": "Look up `key` in map `m`, returning the associated value or `default` (`nil` if omitted) when the key is missing. Alias of `get`.\n\n```sema\n(hash-ref {:a 1 :b 2} :a) ; => 1\n(hash-ref {:a 1} :missing) ; => nil\n(hash-ref {:a 1} :missing \"none\") ; => \"none\"\n```"
},
{
"name": "hashmap/assoc",
"module": "maps",
"section": "HashMaps",
"summary": "Add a key-value pair to a hashmap.",
"examples": [
"(hashmap/assoc (hashmap/new) :a 1) ; hashmap with :a 1"
],
"body": "Add a key-value pair to a hashmap.\n\n```sema\n(hashmap/assoc (hashmap/new) :a 1) ; hashmap with :a 1\n```"
},
{
"name": "hashmap/contains?",
"module": "maps",
"section": "HashMaps",
"summary": "Test if a hashmap contains a key.",
"examples": [
"(hashmap/contains? (hashmap/new :a 1) :a) ; => #t"
],
"body": "Test if a hashmap contains a key.\n\n```sema\n(hashmap/contains? (hashmap/new :a 1) :a) ; => #t\n```"
},
{
"name": "hashmap/get",
"module": "maps",
"section": "HashMaps",
"summary": "Look up a value in a hashmap.",
"examples": [
"(hashmap/get (hashmap/new :a 1) :a) ; => 1"
],
"body": "Look up a value in a hashmap.\n\n```sema\n(hashmap/get (hashmap/new :a 1) :a) ; => 1\n```"
},
{
"name": "hashmap/keys",
"module": "maps",
"section": "HashMaps",
"summary": "Return the keys of a hashmap (unordered).",
"examples": [
"(hashmap/keys (hashmap/new :a 1 :b 2)) ; => (:a :b)"
],
"body": "Return the keys of a hashmap (unordered).\n\n```sema\n(hashmap/keys (hashmap/new :a 1 :b 2)) ; => (:a :b)\n```"
},
{
"name": "hashmap/new",
"module": "maps",
"section": "HashMaps",
"summary": "Create a new hashmap from key-value pairs.",
"examples": [
"(hashmap/new :a 1 :b 2 :c 3) ; create a hashmap\n(hashmap/new) ; empty hashmap"
],
"body": "Create a new hashmap from key-value pairs.\n\n```sema\n(hashmap/new :a 1 :b 2 :c 3) ; create a hashmap\n(hashmap/new) ; empty hashmap\n```"
},
{
"name": "hashmap/to-map",
"module": "maps",
"section": "HashMaps",
"summary": "Convert a hashmap to a sorted map.",
"examples": [
"(hashmap/to-map (hashmap/new :b 2 :a 1)) ; => {:a 1 :b 2}"
],
"body": "Convert a hashmap to a sorted map.\n\n```sema\n(hashmap/to-map (hashmap/new :b 2 :a 1)) ; => {:a 1 :b 2}\n```"
},
{
"name": "keys",
"module": "maps",
"section": "Maps",
"summary": "Return the keys of a map as a list.",
"examples": [
"(keys {:a 1 :b 2}) ; => (:a :b)"
],
"body": "Return the keys of a map as a list.\n\n```sema\n(keys {:a 1 :b 2}) ; => (:a :b)\n```"
},
{
"name": "map/assoc-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Set a value at a nested key path. Creates intermediate maps if they don't exist.",
"examples": [
"(map/assoc-in {:a {:b 1}} [:a :b] 42) ; => {:a {:b 42}}\n(map/assoc-in {} [:a :b :c] 99) ; => {:a {:b {:c 99}}}"
],
"body": "Set a value at a nested key path. Creates intermediate maps if they don't exist.\n\n```sema\n(map/assoc-in {:a {:b 1}} [:a :b] 42) ; => {:a {:b 42}}\n(map/assoc-in {} [:a :b :c] 99) ; => {:a {:b {:c 99}}}\n```"
},
{
"name": "map/deep-merge",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Recursively merge maps. Nested maps are merged rather than replaced. Non-map values in the overlay override the base.",
"examples": [
"(map/deep-merge {:a {:b 1 :c 2}} {:a {:b 99}}) ; => {:a {:b 99 :c 2}}\n(map/deep-merge {:a {:b 1}} {:a 42}) ; => {:a 42}\n(map/deep-merge {:a 1} {:b 2} {:c 3}) ; => {:a 1 :b 2 :c 3}"
],
"body": "Recursively merge maps. Nested maps are merged rather than replaced. Non-map values in the overlay override the base.\n\n```sema\n(map/deep-merge {:a {:b 1 :c 2}} {:a {:b 99}}) ; => {:a {:b 99 :c 2}}\n(map/deep-merge {:a {:b 1}} {:a 42}) ; => {:a 42}\n(map/deep-merge {:a 1} {:b 2} {:c 3}) ; => {:a 1 :b 2 :c 3}\n```"
},
{
"name": "map/entries",
"module": "maps",
"section": "Maps",
"summary": "Return the entries as a list of key-value pairs.",
"examples": [
"(map/entries {:a 1 :b 2}) ; => ((:a 1) (:b 2))"
],
"body": "Return the entries as a list of key-value pairs.\n\n```sema\n(map/entries {:a 1 :b 2}) ; => ((:a 1) (:b 2))\n```"
},
{
"name": "map/except",
"module": "maps",
"section": "HashMaps",
"summary": "Remove specified keys from a map (inverse of `map/select-keys`).",
"examples": [
"(map/except {:a 1 :b 2 :c 3} '(:b)) ; => {:a 1 :c 3}\n(map/except {:a 1 :b 2 :c 3} '(:a :c)) ; => {:b 2}"
],
"body": "Remove specified keys from a map (inverse of `map/select-keys`).\n\n```sema\n(map/except {:a 1 :b 2 :c 3} '(:b)) ; => {:a 1 :c 3}\n(map/except {:a 1 :b 2 :c 3} '(:a :c)) ; => {:b 2}\n```"
},
{
"name": "map/filter",
"module": "maps",
"section": "Higher-Order Map Operations",
"summary": "Filter entries by a predicate that takes key and value.",
"examples": [
"(map/filter (fn (k v) (> v 1)) {:a 1 :b 2 :c 3}) ; => {:b 2 :c 3}"
],
"body": "Filter entries by a predicate that takes key and value.\n\n```sema\n(map/filter (fn (k v) (> v 1)) {:a 1 :b 2 :c 3}) ; => {:b 2 :c 3}\n```"
},
{
"name": "map/from-entries",
"module": "maps",
"section": "Maps",
"summary": "Create a map from a list of key-value pairs.",
"examples": [
"(map/from-entries '((:a 1) (:b 2))) ; => {:a 1 :b 2}"
],
"body": "Create a map from a list of key-value pairs.\n\n```sema\n(map/from-entries '((:a 1) (:b 2))) ; => {:a 1 :b 2}\n```"
},
{
"name": "map/get-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Access a value at a nested key path. Returns `nil` (or a default) if any key is missing.",
"examples": [
"(map/get-in {:a {:b {:c 42}}} [:a :b :c]) ; => 42\n(map/get-in {:a {:b 1}} [:a :c]) ; => nil\n(map/get-in {:a {:b 1}} [:a :c] \"default\") ; => \"default\""
],
"body": "Access a value at a nested key path. Returns `nil` (or a default) if any key is missing.\n\n```sema\n(map/get-in {:a {:b {:c 42}}} [:a :b :c]) ; => 42\n(map/get-in {:a {:b 1}} [:a :c]) ; => nil\n(map/get-in {:a {:b 1}} [:a :c] \"default\") ; => \"default\"\n```"
},
{
"name": "map/map-keys",
"module": "maps",
"section": "Higher-Order Map Operations",
"summary": "Apply a function to every key in a map.",
"examples": [
"(map/map-keys\n (fn (k) (string/to-keyword (string/upper (keyword/to-string k))))\n {:a 1})\n; => {:A 1}"
],
"body": "Apply a function to every key in a map.\n\n```sema\n(map/map-keys\n (fn (k) (string/to-keyword (string/upper (keyword/to-string k))))\n {:a 1})\n; => {:A 1}\n```"
},
{
"name": "map/map-vals",
"module": "maps",
"section": "Higher-Order Map Operations",
"summary": "Apply a function to every value in a map.",
"examples": [
"(map/map-vals (fn (v) (* v 2)) {:a 1 :b 2}) ; => {:a 2 :b 4}"
],
"body": "Apply a function to every value in a map.\n\n```sema\n(map/map-vals (fn (v) (* v 2)) {:a 1 :b 2}) ; => {:a 2 :b 4}\n```"
},
{
"name": "map/new",
"module": "maps",
"section": "Maps",
"summary": "Create a map from key-value pairs.",
"examples": [
"(map/new :a 1 :b 2) ; => {:a 1 :b 2}"
],
"body": "Create a map from key-value pairs.\n\n```sema\n(map/new :a 1 :b 2) ; => {:a 1 :b 2}\n```"
},
{
"name": "map/select-keys",
"module": "maps",
"section": "Higher-Order Map Operations",
"summary": "Select only the given keys from a map.",
"examples": [
"(map/select-keys {:a 1 :b 2 :c 3} '(:a :c)) ; => {:a 1 :c 3}"
],
"body": "Select only the given keys from a map.\n\n```sema\n(map/select-keys {:a 1 :b 2 :c 3} '(:a :c)) ; => {:a 1 :c 3}\n```"
},
{
"name": "map/sort-keys",
"module": "maps",
"section": "HashMaps",
"summary": "Sort a map by its keys. Converts hashmaps to sorted maps.",
"examples": [
"(map/sort-keys (hashmap/new :c 3 :a 1 :b 2)) ; => {:a 1 :b 2 :c 3}"
],
"body": "Sort a map by its keys. Converts hashmaps to sorted maps.\n\n```sema\n(map/sort-keys (hashmap/new :c 3 :a 1 :b 2)) ; => {:a 1 :b 2 :c 3}\n```"
},
{
"name": "map/update",
"module": "maps",
"section": "Higher-Order Map Operations",
"summary": "Update a value at a key by applying a function.",
"examples": [
"(map/update {:a 1} :a (fn (v) (+ v 10))) ; => {:a 11}"
],
"body": "Update a value at a key by applying a function.\n\n```sema\n(map/update {:a 1} :a (fn (v) (+ v 10))) ; => {:a 11}\n```"
},
{
"name": "map/update-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Update a value at a nested key path by applying a function.",
"examples": [
"(map/update-in {:a {:b 10}} [:a :b] #(+ % 1)) ; => {:a {:b 11}}"
],
"body": "Update a value at a nested key path by applying a function.\n\n```sema\n(map/update-in {:a {:b 10}} [:a :b] #(+ % 1)) ; => {:a {:b 11}}\n```"
},
{
"name": "map/zip",
"module": "maps",
"section": "HashMaps",
"summary": "Create a map from a list of keys and a list of values.",
"examples": [
"(map/zip '(:a :b :c) '(1 2 3)) ; => {:a 1 :b 2 :c 3}"
],
"body": "Create a map from a list of keys and a list of values.\n\n```sema\n(map/zip '(:a :b :c) '(1 2 3)) ; => {:a 1 :b 2 :c 3}\n```"
},
{
"name": "merge",
"module": "maps",
"section": "Maps",
"summary": "Merge multiple maps together. Later maps override earlier ones. Works on both maps and hashmaps — the result type matches the first argument.",
"examples": [
"(merge {:a 1} {:b 2} {:c 3}) ; => {:a 1 :b 2 :c 3}\n(merge {:a 1} {:a 99}) ; => {:a 99}\n(merge (hashmap/new :a 1) {:b 2}) ; hashmap with :a and :b"
],
"body": "Merge multiple maps together. Later maps override earlier ones. Works on both maps and hashmaps — the result type matches the first argument.\n\n```sema\n(merge {:a 1} {:b 2} {:c 3}) ; => {:a 1 :b 2 :c 3}\n(merge {:a 1} {:a 99}) ; => {:a 99}\n(merge (hashmap/new :a 1) {:b 2}) ; hashmap with :a and :b\n```"
},
{
"name": "update-in",
"module": "maps",
"section": "Nested Map Operations",
"summary": "Return a copy of `m` with the value at the nested key `path` replaced by `(f current)`, where `current` is the existing value (`nil` if missing). Same as `map/update-in`.",
"params": [
{
"name": "m",
"type": "map"
},
{
"name": "path",
"type": "list | vector"
},
{
"name": "f",
"type": "function"
}
],
"returns": "map",
"examples": [
"(update-in {:a {:b 1}} [:a :b] (fn (x) (+ x 10))) ; => {:a {:b 11}}"
],
"body": "Return a copy of `m` with the value at the nested key `path` replaced by `(f current)`, where `current` is the existing value (`nil` if missing). Same as `map/update-in`.\n\n```sema\n(update-in {:a {:b 1}} [:a :b] (fn (x) (+ x 10))) ; => {:a {:b 11}}\n```"
},
{
"name": "vals",
"module": "maps",
"section": "Maps",
"summary": "Return the values of a map as a list.",
"examples": [
"(vals {:a 1 :b 2}) ; => (1 2)"
],
"body": "Return the values of a map as a list.\n\n```sema\n(vals {:a 1 :b 2}) ; => (1 2)\n```"
},
{
"name": "*",
"module": "math",
"section": "Basic Arithmetic",
"summary": "Multiply numbers together.",
"examples": [
"(* 4 5) ; => 20\n(* 2 3 4) ; => 24\n(*) ; => 1"
],
"body": "Multiply numbers together.\n\n```sema\n(* 4 5) ; => 20\n(* 2 3 4) ; => 24\n(*) ; => 1\n```"
},
{
"name": "+",
"module": "math",
"section": "Basic Arithmetic",
"summary": "Add numbers together. Accepts any number of arguments.",
"examples": [
"(+ 1 2 3) ; => 6\n(+ 10) ; => 10\n(+) ; => 0"
],
"body": "Add numbers together. Accepts any number of arguments.\n\n```sema\n(+ 1 2 3) ; => 6\n(+ 10) ; => 10\n(+) ; => 0\n```"
},
{
"name": "-",
"module": "math",
"section": "Basic Arithmetic",
"summary": "Subtract numbers. With one argument, negates. With multiple, subtracts left to right.",
"examples": [
"(- 10 3) ; => 7\n(- 10 3 2) ; => 5\n(- 5) ; => -5"
],
"body": "Subtract numbers. With one argument, negates. With multiple, subtracts left to right.\n\n```sema\n(- 10 3) ; => 7\n(- 10 3 2) ; => 5\n(- 5) ; => -5\n```"
},
{
"name": "/",
"module": "math",
"section": "Basic Arithmetic",
"summary": "Divide numbers. Returns a float when the division is not exact (so `(/ 10 3)` is `3.3333...`, not `3`). For truncated integer division use [`math/quotient`](#math-quotient).",
"examples": [
"(/ 10 2) ;; => 5\n(/ 10 3) ;; => 3.3333333333333335\n(/ 10.0 3) ;; => 3.3333333333333335"
],
"body": "Divide numbers. Returns a float when the division is not exact (so `(/ 10 3)` is `3.3333...`, not `3`). For truncated integer division use [`math/quotient`](#math-quotient).\n\n```sema\n(/ 10 2) ;; => 5\n(/ 10 3) ;; => 3.3333333333333335\n(/ 10.0 3) ;; => 3.3333333333333335\n```"
},
{
"name": "<",
"module": "math",
"section": "Comparison",
"summary": "Less than. Supports chaining.",
"examples": [
"(< 1 2) ; => #t\n(< 1 2 3) ; => #t\n(< 3 2) ; => #f"
],
"body": "Less than. Supports chaining.\n\n```sema\n(< 1 2) ; => #t\n(< 1 2 3) ; => #t\n(< 3 2) ; => #f\n```"
},
{
"name": "<=",
"module": "math",
"section": "Comparison",
"summary": "Less than or equal.",
"examples": [
"(<= 1 2) ; => #t\n(<= 2 2) ; => #t"
],
"body": "Less than or equal.\n\n```sema\n(<= 1 2) ; => #t\n(<= 2 2) ; => #t\n```"
},
{
"name": "=",
"module": "math",
"section": "Comparison",
"summary": "Numeric equality.",
"examples": [
"(= 1 1) ; => #t\n(= 1 2) ; => #f"
],
"body": "Numeric equality.\n\n```sema\n(= 1 1) ; => #t\n(= 1 2) ; => #f\n```"
},
{
"name": ">",
"module": "math",
"section": "Comparison",
"summary": "Greater than.",
"examples": [
"(> 3 2) ; => #t\n(> 1 2) ; => #f"
],
"body": "Greater than.\n\n```sema\n(> 3 2) ; => #t\n(> 1 2) ; => #f\n```"
},
{
"name": ">=",
"module": "math",
"section": "Comparison",
"summary": "Greater than or equal.",
"examples": [
"(>= 3 2) ; => #t\n(>= 2 2) ; => #t"
],
"body": "Greater than or equal.\n\n```sema\n(>= 3 2) ; => #t\n(>= 2 2) ; => #t\n```"
},
{
"name": "abs",
"module": "math",
"section": "Numeric Utilities",
"summary": "Absolute value.",
"examples": [
"(abs -5) ; => 5\n(abs 3) ; => 3\n(abs -3.14) ; => 3.14"
],
"body": "Absolute value.\n\n```sema\n(abs -5) ; => 5\n(abs 3) ; => 3\n(abs -3.14) ; => 3.14\n```"
},
{
"name": "bit/and",
"module": "math",
"section": "Bitwise Operations",
"summary": "Bitwise AND.",
"examples": [
"(bit/and 5 3) ; => 1\n(bit/and 15 9) ; => 9"
],
"body": "Bitwise AND.\n\n```sema\n(bit/and 5 3) ; => 1\n(bit/and 15 9) ; => 9\n```"
},
{
"name": "bit/not",
"module": "math",
"section": "Bitwise Operations",
"summary": "Bitwise NOT (complement).",
"examples": [
"(bit/not 5) ; => -6"
],
"body": "Bitwise NOT (complement).\n\n```sema\n(bit/not 5) ; => -6\n```"
},
{
"name": "bit/or",
"module": "math",
"section": "Bitwise Operations",
"summary": "Bitwise OR.",
"examples": [
"(bit/or 5 3) ; => 7\n(bit/or 8 4) ; => 12"
],
"body": "Bitwise OR.\n\n```sema\n(bit/or 5 3) ; => 7\n(bit/or 8 4) ; => 12\n```"
},
{
"name": "bit/shift-left",
"module": "math",
"section": "Bitwise Operations",
"summary": "Left bit shift.",
"examples": [
"(bit/shift-left 1 4) ; => 16\n(bit/shift-left 3 2) ; => 12"
],
"body": "Left bit shift.\n\n```sema\n(bit/shift-left 1 4) ; => 16\n(bit/shift-left 3 2) ; => 12\n```"
},
{
"name": "bit/shift-right",
"module": "math",
"section": "Bitwise Operations",
"summary": "Right bit shift.",
"examples": [
"(bit/shift-right 16 2) ; => 4\n(bit/shift-right 8 1) ; => 4"
],
"body": "Right bit shift.\n\n```sema\n(bit/shift-right 16 2) ; => 4\n(bit/shift-right 8 1) ; => 4\n```"
},
{
"name": "bit/xor",
"module": "math",
"section": "Bitwise Operations",
"summary": "Bitwise XOR.",
"examples": [
"(bit/xor 5 3) ; => 6"
],
"body": "Bitwise XOR.\n\n```sema\n(bit/xor 5 3) ; => 6\n```"
},
{
"name": "ceil",
"module": "math",
"section": "Numeric Utilities",
"summary": "Round up to nearest integer.",
"examples": [
"(ceil 3.2) ; => 4\n(ceil -2.7) ; => -2"
],
"body": "Round up to nearest integer.\n\n```sema\n(ceil 3.2) ; => 4\n(ceil -2.7) ; => -2\n```"
},
{
"name": "ceiling",
"module": "math",
"section": "Scheme Aliases",
"summary": "Alias for `ceil`.",
"examples": [
"(ceiling 3.2) ; => 4"
],
"body": "Alias for `ceil`.\n\n```sema\n(ceiling 3.2) ; => 4\n```"
},
{
"name": "cos",
"module": "math",
"section": "Trigonometry",
"summary": "Cosine (argument in radians).",
"examples": [
"(cos 0) ; => 1.0\n(cos pi) ; => -1.0"
],
"body": "Cosine (argument in radians).\n\n```sema\n(cos 0) ; => 1.0\n(cos pi) ; => -1.0\n```"
},
{
"name": "e",
"module": "math",
"section": "Constants",
"summary": "Euler's number (2.71828...).",
"examples": [
"e ; => 2.718281828459045"
],
"body": "Euler's number (2.71828...).\n\n```sema\ne ; => 2.718281828459045\n```"
},
{
"name": "even?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if an integer is even.",
"examples": [
"(even? 4) ; => #t\n(even? 3) ; => #f"
],
"body": "Test if an integer is even.\n\n```sema\n(even? 4) ; => #t\n(even? 3) ; => #f\n```"
},
{
"name": "expt",
"module": "math",
"section": "Scheme Aliases",
"summary": "Alias for `pow` (Scheme name for exponentiation).",
"examples": [
"(expt 2 10) ; => 1024"
],
"body": "Alias for `pow` (Scheme name for exponentiation).\n\n```sema\n(expt 2 10) ; => 1024\n```"
},
{
"name": "float",
"module": "math",
"summary": "Convert a number or numeric string to a float. Signals an error if a string cannot be parsed as a float.",
"params": [
{
"name": "x",
"type": "number | string"
}
],
"returns": "float",
"examples": [
"(float 5) ; => 5.0\n(float \"3.5\") ; => 3.5"
],
"body": "Convert a number or numeric string to a float. Signals an error if a string cannot be parsed as a float.\n\n```sema\n(float 5) ; => 5.0\n(float \"3.5\") ; => 3.5\n```"
},
{
"name": "floor",
"module": "math",
"section": "Numeric Utilities",
"summary": "Round down to nearest integer.",
"examples": [
"(floor 3.7) ; => 3\n(floor -2.3) ; => -3"
],
"body": "Round down to nearest integer.\n\n```sema\n(floor 3.7) ; => 3\n(floor -2.3) ; => -3\n```"
},
{
"name": "int",
"module": "math",
"summary": "Convert a number or numeric string to an integer. Floats are truncated toward zero. Signals an error if a string cannot be parsed as an integer.",
"params": [
{
"name": "x",
"type": "number | string"
}
],
"returns": "int",
"examples": [
"(int 3.9) ; => 3\n(int \"42\") ; => 42"
],
"body": "Convert a number or numeric string to an integer. Floats are truncated toward zero. Signals an error if a string cannot be parsed as an integer.\n\n```sema\n(int 3.9) ; => 3\n(int \"42\") ; => 42\n```"
},
{
"name": "log",
"module": "math",
"section": "Numeric Utilities",
"summary": "Natural logarithm.",
"examples": [
"(log 1) ; => 0.0\n(log 100) ; => 4.605..."
],
"body": "Natural logarithm.\n\n```sema\n(log 1) ; => 0.0\n(log 100) ; => 4.605...\n```"
},
{
"name": "math/acos",
"module": "math",
"section": "Trigonometry",
"summary": "Inverse cosine. Returns radians.",
"examples": [
"(math/acos 0) ; => ~1.5707 (π/2)\n(math/acos 1) ; => 0.0"
],
"body": "Inverse cosine. Returns radians.\n\n```sema\n(math/acos 0) ; => ~1.5707 (π/2)\n(math/acos 1) ; => 0.0\n```"
},
{
"name": "math/asin",
"module": "math",
"section": "Trigonometry",
"summary": "Inverse sine. Returns radians.",
"examples": [
"(math/asin 1) ; => ~1.5707 (π/2)\n(math/asin 0) ; => 0.0"
],
"body": "Inverse sine. Returns radians.\n\n```sema\n(math/asin 1) ; => ~1.5707 (π/2)\n(math/asin 0) ; => 0.0\n```"
},
{
"name": "math/atan",
"module": "math",
"section": "Trigonometry",
"summary": "Inverse tangent. Returns radians.",
"examples": [
"(math/atan 1) ; => ~0.7854 (π/4)\n(math/atan 0) ; => 0.0"
],
"body": "Inverse tangent. Returns radians.\n\n```sema\n(math/atan 1) ; => ~0.7854 (π/4)\n(math/atan 0) ; => 0.0\n```"
},
{
"name": "math/atan2",
"module": "math",
"section": "Trigonometry",
"summary": "Two-argument inverse tangent. Returns the angle in radians between the positive x-axis and the point (x, y).",
"examples": [
"(math/atan2 1 1) ; => ~0.7854 (π/4)\n(math/atan2 0 -1) ; => ~3.1416 (π)"
],
"body": "Two-argument inverse tangent. Returns the angle in radians between the positive x-axis and the point (x, y).\n\n```sema\n(math/atan2 1 1) ; => ~0.7854 (π/4)\n(math/atan2 0 -1) ; => ~3.1416 (π)\n```"
},
{
"name": "math/clamp",
"module": "math",
"section": "Interpolation & Clamping",
"summary": "Clamp a value to a range.",
"examples": [
"(math/clamp 15 0 10) ; => 10\n(math/clamp -5 0 10) ; => 0\n(math/clamp 5 0 10) ; => 5"
],
"body": "Clamp a value to a range.\n\n```sema\n(math/clamp 15 0 10) ; => 10\n(math/clamp -5 0 10) ; => 0\n(math/clamp 5 0 10) ; => 5\n```"
},
{
"name": "math/cosh",
"module": "math",
"section": "Hyperbolic Functions",
"summary": "Hyperbolic cosine.",
"examples": [
"(math/cosh 0) ; => 1.0\n(math/cosh 1) ; => 1.5430..."
],
"body": "Hyperbolic cosine.\n\n```sema\n(math/cosh 0) ; => 1.0\n(math/cosh 1) ; => 1.5430...\n```"
},
{
"name": "math/degrees->radians",
"module": "math",
"section": "Angle Conversion",
"summary": "Convert degrees to radians.",
"examples": [
"(math/degrees->radians 180) ; => 3.14159...\n(math/degrees->radians 90) ; => 1.5707..."
],
"body": "Convert degrees to radians.\n\n```sema\n(math/degrees->radians 180) ; => 3.14159...\n(math/degrees->radians 90) ; => 1.5707...\n```"
},
{
"name": "math/exp",
"module": "math",
"section": "Exponential & Logarithmic",
"summary": "Euler's number raised to a power (e^x).",
"examples": [
"(math/exp 1) ; => 2.71828...\n(math/exp 0) ; => 1.0"
],
"body": "Euler's number raised to a power (e^x).\n\n```sema\n(math/exp 1) ; => 2.71828...\n(math/exp 0) ; => 1.0\n```"
},
{
"name": "math/gcd",
"module": "math",
"section": "Integer Math",
"summary": "Greatest common divisor.",
"examples": [
"(math/gcd 12 8) ; => 4\n(math/gcd 15 10) ; => 5"
],
"body": "Greatest common divisor.\n\n```sema\n(math/gcd 12 8) ; => 4\n(math/gcd 15 10) ; => 5\n```"
},
{
"name": "math/infinite?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if a value is infinite.",
"examples": [
"(math/infinite? math/infinity) ; => #t\n(math/infinite? 42) ; => #f"
],
"body": "Test if a value is infinite.\n\n```sema\n(math/infinite? math/infinity) ; => #t\n(math/infinite? 42) ; => #f\n```"
},
{
"name": "math/infinity",
"module": "math",
"section": "Constants",
"summary": "Positive infinity.",
"examples": [
"math/infinity ; => inf"
],
"body": "Positive infinity.\n\n```sema\nmath/infinity ; => inf\n```"
},
{
"name": "math/lcm",
"module": "math",
"section": "Integer Math",
"summary": "Least common multiple.",
"examples": [
"(math/lcm 4 6) ; => 12\n(math/lcm 3 5) ; => 15"
],
"body": "Least common multiple.\n\n```sema\n(math/lcm 4 6) ; => 12\n(math/lcm 3 5) ; => 15\n```"
},
{
"name": "math/lerp",
"module": "math",
"section": "Interpolation & Clamping",
"summary": "Linear interpolation between two values. `(math/lerp a b t)` returns `a + (b - a) * t`.",
"examples": [
"(math/lerp 0 100 0.5) ; => 50.0\n(math/lerp 0 100 0.25) ; => 25.0\n(math/lerp 10 20 0.0) ; => 10.0"
],
"body": "Linear interpolation between two values. `(math/lerp a b t)` returns `a + (b - a) * t`.\n\n```sema\n(math/lerp 0 100 0.5) ; => 50.0\n(math/lerp 0 100 0.25) ; => 25.0\n(math/lerp 10 20 0.0) ; => 10.0\n```"
},
{
"name": "math/log10",
"module": "math",
"section": "Exponential & Logarithmic",
"summary": "Base-10 logarithm.",
"examples": [
"(math/log10 100) ; => 2.0\n(math/log10 1000) ; => 3.0"
],
"body": "Base-10 logarithm.\n\n```sema\n(math/log10 100) ; => 2.0\n(math/log10 1000) ; => 3.0\n```"
},
{
"name": "math/log2",
"module": "math",
"section": "Exponential & Logarithmic",
"summary": "Base-2 logarithm.",
"examples": [
"(math/log2 8) ; => 3.0\n(math/log2 1024) ; => 10.0"
],
"body": "Base-2 logarithm.\n\n```sema\n(math/log2 8) ; => 3.0\n(math/log2 1024) ; => 10.0\n```"
},
{
"name": "math/map-range",
"module": "math",
"section": "Interpolation & Clamping",
"summary": "Map a value from one range to another. `(math/map-range value in-min in-max out-min out-max)`.",
"examples": [
"(math/map-range 5 0 10 0 100) ; => 50.0\n(math/map-range 0.5 0 1 0 255) ; => 127.5"
],
"body": "Map a value from one range to another. `(math/map-range value in-min in-max out-min out-max)`.\n\n```sema\n(math/map-range 5 0 10 0 100) ; => 50.0\n(math/map-range 0.5 0 1 0 255) ; => 127.5\n```"
},
{
"name": "math/nan",
"module": "math",
"section": "Constants",
"summary": "Not a number.",
"examples": [
"math/nan ; => NaN"
],
"body": "Not a number.\n\n```sema\nmath/nan ; => NaN\n```"
},
{
"name": "math/nan?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if a value is NaN (not a number).",
"examples": [
"(math/nan? math/nan) ; => #t\n(math/nan? 42) ; => #f"
],
"body": "Test if a value is NaN (not a number).\n\n```sema\n(math/nan? math/nan) ; => #t\n(math/nan? 42) ; => #f\n```"
},
{
"name": "math/pow",
"module": "math",
"summary": "Raise `base` to `exponent`. With two non-negative integers the result is an integer; otherwise both operands are treated as floats and a float is returned. Namespaced form of `pow`/`expt`.",
"params": [
{
"name": "base",
"type": "number"
},
{
"name": "exponent",
"type": "number"
}
],
"returns": "number",
"examples": [
"(math/pow 2 10) ; => 1024\n(math/pow 2.0 0.5) ; => 1.4142135623730951"
],
"body": "Raise `base` to `exponent`. With two non-negative integers the result is an integer; otherwise both operands are treated as floats and a float is returned. Namespaced form of `pow`/`expt`.\n\n```sema\n(math/pow 2 10) ; => 1024\n(math/pow 2.0 0.5) ; => 1.4142135623730951\n```"
},
{
"name": "math/quotient",
"module": "math",
"section": "Integer Math",
"summary": "Integer quotient (truncated division).",
"examples": [
"(math/quotient 10 3) ; => 3\n(math/quotient 7 2) ; => 3"
],
"body": "Integer quotient (truncated division).\n\n```sema\n(math/quotient 10 3) ; => 3\n(math/quotient 7 2) ; => 3\n```"
},
{
"name": "math/radians->degrees",
"module": "math",
"section": "Angle Conversion",
"summary": "Convert radians to degrees.",
"examples": [
"(math/radians->degrees pi) ; => 180.0\n(math/radians->degrees 1) ; => 57.295..."
],
"body": "Convert radians to degrees.\n\n```sema\n(math/radians->degrees pi) ; => 180.0\n(math/radians->degrees 1) ; => 57.295...\n```"
},
{
"name": "math/random",
"module": "math",
"section": "Random Numbers",
"summary": "Return a random float between 0.0 (inclusive) and 1.0 (exclusive).",
"examples": [
"(math/random) ; => 0.7291... (varies)"
],
"body": "Return a random float between 0.0 (inclusive) and 1.0 (exclusive).\n\n```sema\n(math/random) ; => 0.7291... (varies)\n```"
},
{
"name": "math/random-int",
"module": "math",
"section": "Random Numbers",
"summary": "Return a random integer in a range (inclusive on both ends).",
"examples": [
"(math/random-int 1 100) ; => 42 (varies)\n(math/random-int 0 9) ; => 7 (varies)"
],
"body": "Return a random integer in a range (inclusive on both ends).\n\n```sema\n(math/random-int 1 100) ; => 42 (varies)\n(math/random-int 0 9) ; => 7 (varies)\n```"
},
{
"name": "math/remainder",
"module": "math",
"section": "Integer Math",
"summary": "Remainder after truncated division.",
"examples": [
"(math/remainder 10 3) ; => 1\n(math/remainder 7 2) ; => 1"
],
"body": "Remainder after truncated division.\n\n```sema\n(math/remainder 10 3) ; => 1\n(math/remainder 7 2) ; => 1\n```"
},
{
"name": "math/sign",
"module": "math",
"section": "Interpolation & Clamping",
"summary": "Return the sign of a number: -1, 0, or 1.",
"examples": [
"(math/sign -5) ; => -1\n(math/sign 0) ; => 0\n(math/sign 42) ; => 1"
],
"body": "Return the sign of a number: -1, 0, or 1.\n\n```sema\n(math/sign -5) ; => -1\n(math/sign 0) ; => 0\n(math/sign 42) ; => 1\n```"
},
{
"name": "math/sinh",
"module": "math",
"section": "Hyperbolic Functions",
"summary": "Hyperbolic sine.",
"examples": [
"(math/sinh 0) ; => 0.0\n(math/sinh 1) ; => 1.1752..."
],
"body": "Hyperbolic sine.\n\n```sema\n(math/sinh 0) ; => 0.0\n(math/sinh 1) ; => 1.1752...\n```"
},
{
"name": "math/tan",
"module": "math",
"section": "Trigonometry",
"summary": "Tangent (argument in radians).",
"examples": [
"(math/tan 0) ; => 0.0\n(math/tan (/ pi 4)); => ~1.0"
],
"body": "Tangent (argument in radians).\n\n```sema\n(math/tan 0) ; => 0.0\n(math/tan (/ pi 4)); => ~1.0\n```"
},
{
"name": "math/tanh",
"module": "math",
"section": "Hyperbolic Functions",
"summary": "Hyperbolic tangent.",
"examples": [
"(math/tanh 0) ; => 0.0\n(math/tanh 1) ; => 0.7615..."
],
"body": "Hyperbolic tangent.\n\n```sema\n(math/tanh 0) ; => 0.0\n(math/tanh 1) ; => 0.7615...\n```"
},
{
"name": "max",
"module": "math",
"section": "Numeric Utilities",
"summary": "Return the largest of 1 or more numbers (the no-arg case errors).",
"examples": [
"(max 1 2 3) ;; => 3\n(max 5) ;; => 5\n(max) ;; error: Arity error: max expects 1+ args, got 0"
],
"body": "Return the largest of 1 or more numbers (the no-arg case errors).\n\n```sema\n(max 1 2 3) ;; => 3\n(max 5) ;; => 5\n(max) ;; error: Arity error: max expects 1+ args, got 0\n```"
},
{
"name": "min",
"module": "math",
"section": "Numeric Utilities",
"summary": "Return the smallest of 1 or more numbers (the no-arg case errors).",
"examples": [
"(min 1 2 3) ;; => 1\n(min 5) ;; => 5\n(min) ;; error: Arity error: min expects 1+ args, got 0"
],
"body": "Return the smallest of 1 or more numbers (the no-arg case errors).\n\n```sema\n(min 1 2 3) ;; => 1\n(min 5) ;; => 5\n(min) ;; error: Arity error: min expects 1+ args, got 0\n```"
},
{
"name": "mod",
"module": "math",
"section": "Basic Arithmetic",
"summary": "Modulo (remainder after division).",
"examples": [
"(mod 10 3) ; => 1\n(mod 7 2) ; => 1"
],
"body": "Modulo (remainder after division).\n\n```sema\n(mod 10 3) ; => 1\n(mod 7 2) ; => 1\n```"
},
{
"name": "modulo",
"module": "math",
"section": "Scheme Aliases",
"summary": "Alias for `mod`.",
"examples": [
"(modulo 10 3) ; => 1"
],
"body": "Alias for `mod`.\n\n```sema\n(modulo 10 3) ; => 1\n```"
},
{
"name": "negative?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if a number is negative.",
"examples": [
"(negative? -1) ; => #t\n(negative? 1) ; => #f"
],
"body": "Test if a number is negative.\n\n```sema\n(negative? -1) ; => #t\n(negative? 1) ; => #f\n```"
},
{
"name": "odd?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if an integer is odd.",
"examples": [
"(odd? 3) ; => #t\n(odd? 4) ; => #f"
],
"body": "Test if an integer is odd.\n\n```sema\n(odd? 3) ; => #t\n(odd? 4) ; => #f\n```"
},
{
"name": "pi",
"module": "math",
"section": "Constants",
"summary": "The mathematical constant π (3.14159...).",
"examples": [
"pi ; => 3.141592653589793"
],
"body": "The mathematical constant π (3.14159...).\n\n```sema\npi ; => 3.141592653589793\n```"
},
{
"name": "positive?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if a number is positive.",
"examples": [
"(positive? 1) ; => #t\n(positive? -1) ; => #f\n(positive? 0) ; => #f"
],
"body": "Test if a number is positive.\n\n```sema\n(positive? 1) ; => #t\n(positive? -1) ; => #f\n(positive? 0) ; => #f\n```"
},
{
"name": "pow",
"module": "math",
"section": "Numeric Utilities",
"summary": "Raise a number to a power.",
"examples": [
"(pow 2 10) ; => 1024\n(pow 3 3) ; => 27"
],
"body": "Raise a number to a power.\n\n```sema\n(pow 2 10) ; => 1024\n(pow 3 3) ; => 27\n```"
},
{
"name": "round",
"module": "math",
"section": "Numeric Utilities",
"summary": "Round to nearest integer.",
"examples": [
"(round 3.5) ; => 4\n(round 3.4) ; => 3"
],
"body": "Round to nearest integer.\n\n```sema\n(round 3.5) ; => 4\n(round 3.4) ; => 3\n```"
},
{
"name": "sin",
"module": "math",
"section": "Trigonometry",
"summary": "Sine (argument in radians).",
"examples": [
"(sin 0) ; => 0.0\n(sin pi) ; => ~0.0"
],
"body": "Sine (argument in radians).\n\n```sema\n(sin 0) ; => 0.0\n(sin pi) ; => ~0.0\n```"
},
{
"name": "sqrt",
"module": "math",
"section": "Numeric Utilities",
"summary": "Square root.",
"examples": [
"(sqrt 16) ; => 4.0\n(sqrt 2) ; => 1.4142..."
],
"body": "Square root.\n\n```sema\n(sqrt 16) ; => 4.0\n(sqrt 2) ; => 1.4142...\n```"
},
{
"name": "truncate",
"module": "math",
"section": "Scheme Aliases",
"summary": "Truncate toward zero.",
"examples": [
"(truncate 3.7) ; => 3\n(truncate -3.7) ; => -3"
],
"body": "Truncate toward zero.\n\n```sema\n(truncate 3.7) ; => 3\n(truncate -3.7) ; => -3\n```"
},
{
"name": "zero?",
"module": "math",
"section": "Numeric Predicates",
"summary": "Test if a number is zero.",
"examples": [
"(zero? 0) ; => #t\n(zero? 1) ; => #f"
],
"body": "Test if a number is zero.\n\n```sema\n(zero? 0) ; => #t\n(zero? 1) ; => #f\n```"
},
{
"name": "message/content",
"module": "message",
"summary": "Return the text content of a message.",
"params": [
{
"name": "msg",
"type": "message"
}
],
"returns": "string",
"examples": [
"(message/content msg) ; => \"Hello, world\""
],
"body": "Return the text content of a message.\n\n```sema\n(message/content msg) ; => \"Hello, world\"\n```"
},
{
"name": "message/role",
"module": "message",
"summary": "Return the role of a message as a keyword (`:system`, `:user`, `:assistant`, or `:tool`).",
"params": [
{
"name": "msg",
"type": "message"
}
],
"returns": "keyword",
"examples": [
"(message/role msg) ; => :user"
],
"body": "Return the role of a message as a keyword (`:system`, `:user`, `:assistant`, or `:tool`).\n\n```sema\n(message/role msg) ; => :user\n```"
},
{
"name": "message/with-image",
"module": "message",
"summary": "Build a multimodal message with text plus an attached image. Role is `:system`, `:user`, `:assistant`, or `:tool`; the image is a bytevector that is base64-encoded. The media type is auto-detected unless overridden via opts `:media-type`.",
"params": [
{
"name": "role",
"type": "keyword"
},
{
"name": "text",
"type": "string"
},
{
"name": "image",
"type": "bytevector"
},
{
"name": "opts",
"type": "map"
}
],
"returns": "message",
"examples": [
"(message/with-image :user \"Describe this picture\" png-bytes {:media-type \"image/png\"})"
],
"body": "Build a multimodal message with text plus an attached image. Role is `:system`, `:user`, `:assistant`, or `:tool`; the image is a bytevector that is base64-encoded. The media type is auto-detected unless overridden via opts `:media-type`.\n\n```sema\n(message/with-image :user \"Describe this picture\" png-bytes {:media-type \"image/png\"})\n```"
},
{
"name": "pdf/extract-text",
"module": "pdf",
"section": "Text Extraction",
"summary": "Extract all text from a PDF file, concatenated across all pages.",
"examples": [
"(pdf/extract-text \"invoice.pdf\")\n; => \"Invoice\\nDate: 2025-01-15\\nAmount: $50.00 USD\\n...\"\n\n;; Clean up whitespace for LLM processing\n(text/clean-whitespace (pdf/extract-text \"invoice.pdf\"))\n; => \"Invoice Date: 2025-01-15 Amount: $50.00 USD ...\""
],
"body": "Extract all text from a PDF file, concatenated across all pages.\n\n```sema\n(pdf/extract-text \"invoice.pdf\")\n; => \"Invoice\\nDate: 2025-01-15\\nAmount: $50.00 USD\\n...\"\n\n;; Clean up whitespace for LLM processing\n(text/clean-whitespace (pdf/extract-text \"invoice.pdf\"))\n; => \"Invoice Date: 2025-01-15 Amount: $50.00 USD ...\"\n```"
},
{
"name": "pdf/extract-text-pages",
"module": "pdf",
"section": "Text Extraction",
"summary": "Extract text from a PDF, returning a list of strings — one per page.",
"examples": [
"(pdf/extract-text-pages \"report.pdf\")\n; => (\"Page 1 content...\" \"Page 2 content...\" \"Page 3 content...\")\n\n;; Get text from a specific page\n(nth (pdf/extract-text-pages \"report.pdf\") 0)\n; => \"Page 1 content...\"\n\n;; Process each page separately\n(for-each\n (fn (page-text)\n (println (format \"Page has ~a words\" (text/word-count page-text))))\n (pdf/extract-text-pages \"report.pdf\"))"
],
"body": "Extract text from a PDF, returning a list of strings — one per page.\n\n```sema\n(pdf/extract-text-pages \"report.pdf\")\n; => (\"Page 1 content...\" \"Page 2 content...\" \"Page 3 content...\")\n\n;; Get text from a specific page\n(nth (pdf/extract-text-pages \"report.pdf\") 0)\n; => \"Page 1 content...\"\n\n;; Process each page separately\n(for-each\n (fn (page-text)\n (println (format \"Page has ~a words\" (text/word-count page-text))))\n (pdf/extract-text-pages \"report.pdf\"))\n```"
},
{
"name": "pdf/metadata",
"module": "pdf",
"section": "Metadata",
"summary": "Return a map of PDF metadata fields. Always includes `:pages`; other fields (`:title`, `:author`, `:subject`, `:creator`, `:producer`) are included when present in the PDF.",
"examples": [
"(pprint (pdf/metadata \"document.pdf\"))\n; => {:author \"John Doe\"\n; :creator \"LibreOffice Writer\"\n; :pages 5\n; :producer \"LibreOffice\"\n; :title \"Quarterly Report\"}\n\n;; Access individual fields\n(get (pdf/metadata \"document.pdf\") :title)\n; => \"Quarterly Report\"\n\n(get (pdf/metadata \"document.pdf\") :pages)\n; => 5"
],
"body": "Return a map of PDF metadata fields. Always includes `:pages`; other fields (`:title`, `:author`, `:subject`, `:creator`, `:producer`) are included when present in the PDF.\n\n```sema\n(pprint (pdf/metadata \"document.pdf\"))\n; => {:author \"John Doe\"\n; :creator \"LibreOffice Writer\"\n; :pages 5\n; :producer \"LibreOffice\"\n; :title \"Quarterly Report\"}\n\n;; Access individual fields\n(get (pdf/metadata \"document.pdf\") :title)\n; => \"Quarterly Report\"\n\n(get (pdf/metadata \"document.pdf\") :pages)\n; => 5\n```"
},
{
"name": "pdf/page-count",
"module": "pdf",
"section": "Metadata",
"summary": "Return the number of pages in a PDF.",
"examples": [
"(pdf/page-count \"report.pdf\")\n; => 12"
],
"body": "Return the number of pages in a PDF.\n\n```sema\n(pdf/page-count \"report.pdf\")\n; => 12\n```"
},
{
"name": "pio/assemble",
"module": "pio",
"section": "PIO Programs",
"summary": "Assemble a list of PIO instruction maps (interspersed with label symbols and the `:wrap-target` / `:wrap` keyword markers) into a binary program. Resolves labels, enforces the 32-instruction limit, and returns a map with `:instructions` (a little-endian bytevector of 16-bit words), `:length`, `:wrap-target`, and `:wrap`. The optional `config` map accepts `:side-set-bits` (0..5) and `:side-set-opt` (bool), which reserve delay/side-set bits accordingly.",
"params": [
{
"name": "program",
"type": "list"
},
{
"name": "config",
"type": "map"
}
],
"returns": "map",
"examples": [
"(pio/assemble\n (list 'loop\n (pio/set :pins 1)\n (pio/set :pins 0)\n (pio/jmp :always 'loop))\n {:side-set-bits 0})"
],
"body": "Assemble a list of PIO instruction maps (interspersed with label symbols and the `:wrap-target` / `:wrap` keyword markers) into a binary program. Resolves labels, enforces the 32-instruction limit, and returns a map with `:instructions` (a little-endian bytevector of 16-bit words), `:length`, `:wrap-target`, and `:wrap`. The optional `config` map accepts `:side-set-bits` (0..5) and `:side-set-opt` (bool), which reserve delay/side-set bits accordingly.\n\n```sema\n(pio/assemble\n (list 'loop\n (pio/set :pins 1)\n (pio/set :pins 0)\n (pio/jmp :always 'loop))\n {:side-set-bits 0})\n```"
},
{
"name": "pio/delay",
"module": "pio",
"section": "PIO Instructions",
"summary": "Attach a post-instruction delay of `cycles` (0..31) to an existing PIO instruction map, stalling the state machine for that many cycles after the instruction runs. Returns a copy of `instr` with the delay field added. The available delay range shrinks as more side-set bits are configured.",
"params": [
{
"name": "cycles",
"type": "int"
},
{
"name": "instr",
"type": "map"
}
],
"returns": "map",
"examples": [
"(pio/delay 5 (pio/set :pins 1))"
],
"body": "Attach a post-instruction delay of `cycles` (0..31) to an existing PIO instruction map, stalling the state machine for that many cycles after the instruction runs. Returns a copy of `instr` with the delay field added. The available delay range shrinks as more side-set bits are configured.\n\n```sema\n(pio/delay 5 (pio/set :pins 1))\n```"
},
{
"name": "pio/in",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `in` instruction that shifts `bits` (1..32) from `source` into the ISR. Valid sources: `:pins`, `:x`, `:y`, `:null`, `:isr`, `:osr`.",
"params": [
{
"name": "source",
"type": "keyword"
},
{
"name": "bits",
"type": "int"
}
],
"returns": "map",
"examples": [
"(pio/in :pins 8) ; shift 8 bits from the input pins into the ISR"
],
"body": "Build an RP2040 PIO `in` instruction that shifts `bits` (1..32) from `source` into the ISR. Valid sources: `:pins`, `:x`, `:y`, `:null`, `:isr`, `:osr`.\n\n```sema\n(pio/in :pins 8) ; shift 8 bits from the input pins into the ISR\n```"
},
{
"name": "pio/irq",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `irq` instruction that operates on IRQ flag `index` (0..7). `mode` is one of `:set` (raise the flag), `:wait` (raise it and stall until cleared), or `:clear` (lower it). Pass an optional trailing `:rel` keyword to make the index relative to the state machine.",
"params": [
{
"name": "mode",
"type": "keyword"
},
{
"name": "index",
"type": "int"
},
{
"name": "rel",
"type": "keyword"
}
],
"returns": "map",
"examples": [
"(pio/irq :set 0)\n(pio/irq :wait 1 :rel)"
],
"body": "Build an RP2040 PIO `irq` instruction that operates on IRQ flag `index` (0..7). `mode` is one of `:set` (raise the flag), `:wait` (raise it and stall until cleared), or `:clear` (lower it). Pass an optional trailing `:rel` keyword to make the index relative to the state machine.\n\n```sema\n(pio/irq :set 0)\n(pio/irq :wait 1 :rel)\n```"
},
{
"name": "pio/jmp",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `jmp` instruction. Called with a single `target` symbol it is an unconditional jump (`:always`); called with `(pio/jmp cond target)` it jumps only when the condition holds. Valid conditions: `:always`, `:!x`, `:x--`, `:!y`, `:y--`, `:x!=y`, `:pin`, `:!osre`. `target` is a label symbol resolved at assembly time.",
"params": [
{
"name": "cond",
"type": "keyword"
},
{
"name": "target",
"type": "symbol"
}
],
"returns": "map",
"examples": [
"(pio/jmp :x-- 'loop)"
],
"body": "Build an RP2040 PIO `jmp` instruction. Called with a single `target` symbol it is an unconditional jump (`:always`); called with `(pio/jmp cond target)` it jumps only when the condition holds. Valid conditions: `:always`, `:!x`, `:x--`, `:!y`, `:y--`, `:x!=y`, `:pin`, `:!osre`. `target` is a label symbol resolved at assembly time.\n\n```sema\n(pio/jmp :x-- 'loop)\n```"
},
{
"name": "pio/mov",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `mov` instruction copying `source` to `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:exec`, `:pc`. Valid sources: `:pins`, `:x`, `:y`, `:null`, `:status`, `:isr`, `:osr`. An optional third `op` keyword applies a transform: `:invert` (bitwise NOT) or `:reverse` (bit-reverse).",
"params": [
{
"name": "dest",
"type": "keyword"
},
{
"name": "source",
"type": "keyword"
},
{
"name": "op",
"type": "keyword"
}
],
"returns": "map",
"examples": [
"(pio/mov :x :osr) ; copy OSR into X\n(pio/mov :y :x :invert) ; copy ~X into Y"
],
"body": "Build an RP2040 PIO `mov` instruction copying `source` to `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:exec`, `:pc`. Valid sources: `:pins`, `:x`, `:y`, `:null`, `:status`, `:isr`, `:osr`. An optional third `op` keyword applies a transform: `:invert` (bitwise NOT) or `:reverse` (bit-reverse).\n\n```sema\n(pio/mov :x :osr) ; copy OSR into X\n(pio/mov :y :x :invert) ; copy ~X into Y\n```"
},
{
"name": "pio/nop",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `nop` instruction (encoded as `mov y, y`, which has no side effects). Returns an instruction map for use in a program passed to `pio/assemble`.",
"returns": "map",
"examples": [
"(pio/nop) ; => an instruction map"
],
"body": "Build an RP2040 PIO `nop` instruction (encoded as `mov y, y`, which has no side effects). Returns an instruction map for use in a program passed to `pio/assemble`.\n\n```sema\n(pio/nop) ; => an instruction map\n```"
},
{
"name": "pio/out",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `out` instruction that shifts `bits` (1..32) out of the OSR into `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:null`, `:pindirs`, `:pc`, `:isr`, `:exec`.",
"params": [
{
"name": "dest",
"type": "keyword"
},
{
"name": "bits",
"type": "int"
}
],
"returns": "map",
"examples": [
"(pio/out :pins 1) ; shift 1 bit from the OSR onto the output pins"
],
"body": "Build an RP2040 PIO `out` instruction that shifts `bits` (1..32) out of the OSR into `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:null`, `:pindirs`, `:pc`, `:isr`, `:exec`.\n\n```sema\n(pio/out :pins 1) ; shift 1 bit from the OSR onto the output pins\n```"
},
{
"name": "pio/pull",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `pull` instruction that moves a word from the TX FIFO into the OSR. Accepts up to two option keywords: `:block` (default) / `:no-block` controls whether to stall when the FIFO is empty, and `:ifempty` only pulls once the OSR has reached its shift threshold.",
"params": [
{
"name": "opts",
"type": "keyword"
}
],
"returns": "map",
"examples": [
"(pio/pull :ifempty :block)"
],
"body": "Build an RP2040 PIO `pull` instruction that moves a word from the TX FIFO into the OSR. Accepts up to two option keywords: `:block` (default) / `:no-block` controls whether to stall when the FIFO is empty, and `:ifempty` only pulls once the OSR has reached its shift threshold.\n\n```sema\n(pio/pull :ifempty :block)\n```"
},
{
"name": "pio/push",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `push` instruction that moves the ISR contents into the RX FIFO. Accepts up to two option keywords: `:block` (default) / `:no-block` controls whether to stall when the FIFO is full, and `:iffull` only pushes once the ISR has reached its shift threshold.",
"params": [
{
"name": "opts",
"type": "keyword"
}
],
"returns": "map",
"examples": [
"(pio/push :iffull :block)"
],
"body": "Build an RP2040 PIO `push` instruction that moves the ISR contents into the RX FIFO. Accepts up to two option keywords: `:block` (default) / `:no-block` controls whether to stall when the FIFO is full, and `:iffull` only pushes once the ISR has reached its shift threshold.\n\n```sema\n(pio/push :iffull :block)\n```"
},
{
"name": "pio/set",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `set` instruction that writes an immediate `value` (0..31) to `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:pindirs`.",
"params": [
{
"name": "dest",
"type": "keyword"
},
{
"name": "value",
"type": "int"
}
],
"returns": "map",
"examples": [
"(pio/set :pins 1) ; drive the set pins high\n(pio/set :x 0) ; load 0 into X"
],
"body": "Build an RP2040 PIO `set` instruction that writes an immediate `value` (0..31) to `dest`. Valid destinations: `:pins`, `:x`, `:y`, `:pindirs`.\n\n```sema\n(pio/set :pins 1) ; drive the set pins high\n(pio/set :x 0) ; load 0 into X\n```"
},
{
"name": "pio/side",
"module": "pio",
"section": "PIO Instructions",
"summary": "Attach a side-set `value` (0..31) to an existing PIO instruction map, setting the side-set pins in the same cycle the instruction executes. Returns a copy of `instr` with the side-set field added. The side-set width must be configured via the `:side-set-bits` option to `pio/assemble`.",
"params": [
{
"name": "value",
"type": "int"
},
{
"name": "instr",
"type": "map"
}
],
"returns": "map",
"examples": [
"(pio/side 1 (pio/nop)) ; nop while driving side-set pins to 1"
],
"body": "Attach a side-set `value` (0..31) to an existing PIO instruction map, setting the side-set pins in the same cycle the instruction executes. Returns a copy of `instr` with the side-set field added. The side-set width must be configured via the `:side-set-bits` option to `pio/assemble`.\n\n```sema\n(pio/side 1 (pio/nop)) ; nop while driving side-set pins to 1\n```"
},
{
"name": "pio/wait",
"module": "pio",
"section": "PIO Instructions",
"summary": "Build an RP2040 PIO `wait` instruction that stalls until `source` at `index` matches `polarity` (0 or 1). Valid sources: `:gpio`, `:pin`, `:irq`. `index` is in the range 0..31. Pass an optional trailing `:rel` keyword to make an IRQ index relative to the state machine.",
"params": [
{
"name": "polarity",
"type": "int"
},
{
"name": "source",
"type": "keyword"
},
{
"name": "index",
"type": "int"
},
{
"name": "rel",
"type": "keyword"
}
],
"returns": "map",
"examples": [
"(pio/wait 1 :pin 0) ; wait for pin 0 to go high\n(pio/wait 1 :irq 0 :rel) ; wait for relative IRQ 0"
],
"body": "Build an RP2040 PIO `wait` instruction that stalls until `source` at `index` matches `polarity` (0 or 1). Valid sources: `:gpio`, `:pin`, `:irq`. `index` is in the range 0..31. Pass an optional trailing `:rel` keyword to make an IRQ index relative to the state machine.\n\n```sema\n(pio/wait 1 :pin 0) ; wait for pin 0 to go high\n(pio/wait 1 :irq 0 :rel) ; wait for relative IRQ 0\n```"
},
{
"name": "web/user-agent",
"module": "playground",
"section": "Web-Only Functions",
"summary": "Return the browser's `navigator.userAgent` string. Works in all browsers.",
"examples": [
"(web/user-agent)\n; => \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...\""
],
"body": "Return the browser's `navigator.userAgent` string. Works in all browsers.\n\n```sema\n(web/user-agent)\n; => \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...\"\n```"
},
{
"name": "web/user-agent-data",
"module": "playground",
"section": "Web-Only Functions",
"summary": "Return structured browser information from `navigator.userAgentData`. Returns a map on Chromium-based browsers (Chrome, Edge, Opera), `nil` on Firefox and Safari.",
"examples": [
"(web/user-agent-data)\n; Chromium => {:mobile false :platform \"macOS\" :brands (\"Chromium/120\" \"Google Chrome/120\")}\n; Firefox/Safari => nil"
],
"body": "Return structured browser information from `navigator.userAgentData`. Returns a map on Chromium-based browsers (Chrome, Edge, Opera), `nil` on Firefox and Safari.\n\n```sema\n(web/user-agent-data)\n; Chromium => {:mobile false :platform \"macOS\" :brands (\"Chromium/120\" \"Google Chrome/120\")}\n; Firefox/Safari => nil\n```\n\n`userAgentData` is the modern replacement for UA string parsing — it returns structured, reliable data instead of a messy string. However, it's Chromium-only. Use `web/user-agent` for cross-browser compatibility."
},
{
"name": "=",
"module": "predicates",
"section": "Equality",
"summary": "Numeric equality.",
"examples": [
"(= 1 1) ; => #t\n(= 1 1.0) ; => #t\n(= 1 2) ; => #f"
],
"body": "Numeric equality.\n\n```sema\n(= 1 1) ; => #t\n(= 1 1.0) ; => #t\n(= 1 2) ; => #f\n```"
},
{
"name": "agent?",
"module": "predicates",
"section": "LLM Type Predicates",
"summary": "Test if a value is an agent.",
"examples": [
"(defagent my-agent {:system \"test\"})\n(agent? my-agent) ; => #t\n(agent? 42) ; => #f"
],
"body": "Test if a value is an agent.\n\n```sema\n(defagent my-agent {:system \"test\"})\n(agent? my-agent) ; => #t\n(agent? 42) ; => #f\n```"
},
{
"name": "bool?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a boolean. `boolean?` is an alias.",
"examples": [
"(bool? #t) ; => #t\n(bool? 0) ; => #f"
],
"body": "Test if a value is a boolean. `boolean?` is an alias.\n\n```sema\n(bool? #t) ; => #t\n(bool? 0) ; => #f\n```"
},
{
"name": "boolean?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Return `#t` if `x` is a boolean (`#t` or `#f`). Alias of `bool?`.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "bool",
"examples": [
"(boolean? #t) ; => #t\n(boolean? 0) ; => #f"
],
"body": "Return `#t` if `x` is a boolean (`#t` or `#f`). Alias of `bool?`.\n\n```sema\n(boolean? #t) ; => #t\n(boolean? 0) ; => #f\n```"
},
{
"name": "bytevector?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a bytevector.",
"examples": [
"(bytevector? #u8()) ; => #t\n(bytevector? '()) ; => #f"
],
"body": "Test if a value is a bytevector.\n\n```sema\n(bytevector? #u8()) ; => #t\n(bytevector? '()) ; => #f\n```"
},
{
"name": "char?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a character.",
"examples": [
"(char? #\\a) ; => #t\n(char? \"a\") ; => #f"
],
"body": "Test if a value is a character.\n\n```sema\n(char? #\\a) ; => #t\n(char? \"a\") ; => #f\n```"
},
{
"name": "conversation?",
"module": "predicates",
"section": "LLM Type Predicates",
"summary": "Test if a value is a conversation.",
"examples": [
"(conversation? (conversation/new {})) ; => #t"
],
"body": "Test if a value is a conversation.\n\n```sema\n(conversation? (conversation/new {})) ; => #t\n```"
},
{
"name": "empty?",
"module": "predicates",
"section": "Emptiness Predicates",
"summary": "Test if a collection, string, or `nil` is empty. Accepts strings, lists, vectors, maps, and `nil`.",
"examples": [
"(empty? \"\") ;; => #t\n(empty? '()) ;; => #t\n(empty? nil) ;; => #t\n(empty? \"hello\") ;; => #f\n(empty? [1 2 3]) ;; => #f"
],
"body": "Test if a collection, string, or `nil` is empty. Accepts strings, lists, vectors, maps, and `nil`.\n\n```sema\n(empty? \"\") ;; => #t\n(empty? '()) ;; => #t\n(empty? nil) ;; => #t\n(empty? \"hello\") ;; => #f\n(empty? [1 2 3]) ;; => #f\n```"
},
{
"name": "eq?",
"module": "predicates",
"section": "Equality",
"summary": "Test structural equality. `equal?` is an alias.",
"examples": [
"(eq? 'a 'a) ; => #t\n(eq? '(1 2) '(1 2)) ; => #t\n(eq? 1 2) ; => #f"
],
"body": "Test structural equality. `equal?` is an alias.\n\n```sema\n(eq? 'a 'a) ; => #t\n(eq? '(1 2) '(1 2)) ; => #t\n(eq? 1 2) ; => #f\n```"
},
{
"name": "equal?",
"module": "predicates",
"section": "Equality",
"summary": "Test structural (deep) equality of two values.",
"params": [
{
"name": "a",
"type": "any"
},
{
"name": "b",
"type": "any"
}
],
"returns": "bool",
"examples": [
"(equal? '(1 2) '(1 2)) ; => #t\n(equal? \"ab\" \"ab\") ; => #t\n(equal? 1 2) ; => #f"
],
"body": "Test structural (deep) equality of two values.\n\n```sema\n(equal? '(1 2) '(1 2)) ; => #t\n(equal? \"ab\" \"ab\") ; => #t\n(equal? 1 2) ; => #f\n```"
},
{
"name": "even?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if an integer is even.",
"examples": [
"(even? 4) ; => #t\n(even? 3) ; => #f"
],
"body": "Test if an integer is even.\n\n```sema\n(even? 4) ; => #t\n(even? 3) ; => #f\n```"
},
{
"name": "float?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a value is a floating-point number.",
"examples": [
"(float? 3.14) ; => #t\n(float? 42) ; => #f"
],
"body": "Test if a value is a floating-point number.\n\n```sema\n(float? 3.14) ; => #t\n(float? 42) ; => #f\n```"
},
{
"name": "fn?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a function. `procedure?` is an alias.",
"examples": [
"(fn? car) ; => #t\n(fn? 42) ; => #f"
],
"body": "Test if a value is a function. `procedure?` is an alias.\n\n```sema\n(fn? car) ; => #t\n(fn? 42) ; => #f\n```"
},
{
"name": "integer?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a value is an integer.",
"examples": [
"(integer? 42) ; => #t\n(integer? 3.14) ; => #f"
],
"body": "Test if a value is an integer.\n\n```sema\n(integer? 42) ; => #t\n(integer? 3.14) ; => #f\n```"
},
{
"name": "keyword?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a keyword.",
"examples": [
"(keyword? :k) ; => #t\n(keyword? \"k\") ; => #f"
],
"body": "Test if a value is a keyword.\n\n```sema\n(keyword? :k) ; => #t\n(keyword? \"k\") ; => #f\n```"
},
{
"name": "list?",
"module": "predicates",
"section": "Collection Predicates",
"summary": "Test if a value is a list.",
"examples": [
"(list? '(1)) ; => #t\n(list? 42) ; => #f"
],
"body": "Test if a value is a list.\n\n```sema\n(list? '(1)) ; => #t\n(list? 42) ; => #f\n```"
},
{
"name": "map?",
"module": "predicates",
"section": "Collection Predicates",
"summary": "Test if a value is a map.",
"examples": [
"(map? {:a 1}) ; => #t\n(map? '()) ; => #f"
],
"body": "Test if a value is a map.\n\n```sema\n(map? {:a 1}) ; => #t\n(map? '()) ; => #f\n```"
},
{
"name": "message?",
"module": "predicates",
"section": "LLM Type Predicates",
"summary": "Test if a value is an LLM message.",
"examples": [
"(message? (message :user \"hi\")) ; => #t"
],
"body": "Test if a value is an LLM message.\n\n```sema\n(message? (message :user \"hi\")) ; => #t\n```"
},
{
"name": "negative?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a number is negative.",
"examples": [
"(negative? -1) ; => #t\n(negative? 1) ; => #f"
],
"body": "Test if a number is negative.\n\n```sema\n(negative? -1) ; => #t\n(negative? 1) ; => #f\n```"
},
{
"name": "nil?",
"module": "predicates",
"section": "Emptiness Predicates",
"summary": "Test if a value is `nil` specifically (not the empty list).",
"examples": [
"(nil? nil) ;; => #t\n(nil? '()) ;; => #f\n(nil? 0) ;; => #f"
],
"body": "Test if a value is `nil` specifically (not the empty list).\n\n```sema\n(nil? nil) ;; => #t\n(nil? '()) ;; => #f\n(nil? 0) ;; => #f\n```"
},
{
"name": "not",
"module": "predicates",
"section": "Logic",
"summary": "Logical negation. Returns `#t` when `x` is falsy (`#f` or `nil`) and `#f` otherwise.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "bool",
"examples": [
"(not #f) ; => #t\n(not nil) ; => #t\n(not 0) ; => #f\n(not '()) ; => #f"
],
"body": "Logical negation. Returns `#t` when `x` is falsy (`#f` or `nil`) and `#f` otherwise.\n\n```sema\n(not #f) ; => #t\n(not nil) ; => #t\n(not 0) ; => #f\n(not '()) ; => #f\n```"
},
{
"name": "null?",
"module": "predicates",
"section": "Emptiness Predicates",
"summary": "Test if a value is the empty list or `nil`.",
"examples": [
"(null? '()) ;; => #t\n(null? nil) ;; => #t\n(null? '(1)) ;; => #f"
],
"body": "Test if a value is the empty list or `nil`.\n\n```sema\n(null? '()) ;; => #t\n(null? nil) ;; => #t\n(null? '(1)) ;; => #f\n```"
},
{
"name": "number?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a value is a number (integer or float).",
"examples": [
"(number? 42) ; => #t\n(number? 3.14) ; => #t\n(number? \"42\") ; => #f"
],
"body": "Test if a value is a number (integer or float).\n\n```sema\n(number? 42) ; => #t\n(number? 3.14) ; => #t\n(number? \"42\") ; => #f\n```"
},
{
"name": "odd?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if an integer is odd.",
"examples": [
"(odd? 3) ; => #t\n(odd? 4) ; => #f"
],
"body": "Test if an integer is odd.\n\n```sema\n(odd? 3) ; => #t\n(odd? 4) ; => #f\n```"
},
{
"name": "pair?",
"module": "predicates",
"section": "Collection Predicates",
"summary": "Test if a value is a non-empty list (Scheme compatibility).",
"examples": [
"(pair? '(1 2)) ; => #t\n(pair? '()) ; => #f"
],
"body": "Test if a value is a non-empty list (Scheme compatibility).\n\n```sema\n(pair? '(1 2)) ; => #t\n(pair? '()) ; => #f\n```"
},
{
"name": "positive?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a number is positive.",
"examples": [
"(positive? 1) ; => #t\n(positive? -1) ; => #f"
],
"body": "Test if a number is positive.\n\n```sema\n(positive? 1) ; => #t\n(positive? -1) ; => #f\n```"
},
{
"name": "procedure?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Return `#t` if `x` is callable (a lambda or native function). Alias of `fn?`.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "bool",
"examples": [
"(procedure? car) ; => #t\n(procedure? (fn (x) x)) ; => #t\n(procedure? 42) ; => #f"
],
"body": "Return `#t` if `x` is callable (a lambda or native function). Alias of `fn?`.\n\n```sema\n(procedure? car) ; => #t\n(procedure? (fn (x) x)) ; => #t\n(procedure? 42) ; => #f\n```"
},
{
"name": "promise-forced?",
"module": "predicates",
"section": "Promise Predicates",
"summary": "Test if a promise has been forced (evaluated).",
"examples": [
"(define p (delay (+ 1 2)))\n(promise-forced? p) ; => #f\n(force p)\n(promise-forced? p) ; => #t"
],
"body": "Test if a promise has been forced (evaluated).\n\n```sema\n(define p (delay (+ 1 2)))\n(promise-forced? p) ; => #f\n(force p)\n(promise-forced? p) ; => #t\n```"
},
{
"name": "promise?",
"module": "predicates",
"section": "Promise Predicates",
"summary": "Test if a value is a promise (created with `delay`).",
"examples": [
"(promise? (delay 1)) ; => #t\n(promise? 42) ; => #f"
],
"body": "Test if a value is a promise (created with `delay`).\n\n```sema\n(promise? (delay 1)) ; => #t\n(promise? 42) ; => #f\n```"
},
{
"name": "prompt?",
"module": "predicates",
"section": "LLM Type Predicates",
"summary": "Test if a value is an LLM prompt.",
"examples": [
"(prompt? (prompt (user \"hi\"))) ; => #t"
],
"body": "Test if a value is an LLM prompt.\n\n```sema\n(prompt? (prompt (user \"hi\"))) ; => #t\n```"
},
{
"name": "record?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a record instance.",
"examples": [
"(record? my-record) ; => #t\n(record? 42) ; => #f"
],
"body": "Test if a value is a record instance.\n\n```sema\n(record? my-record) ; => #t\n(record? 42) ; => #f\n```"
},
{
"name": "string?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a string.",
"examples": [
"(string? \"hi\") ; => #t\n(string? 42) ; => #f"
],
"body": "Test if a value is a string.\n\n```sema\n(string? \"hi\") ; => #t\n(string? 42) ; => #f\n```"
},
{
"name": "symbol?",
"module": "predicates",
"section": "Type Predicates",
"summary": "Test if a value is a symbol.",
"examples": [
"(symbol? 'x) ; => #t\n(symbol? \"x\") ; => #f"
],
"body": "Test if a value is a symbol.\n\n```sema\n(symbol? 'x) ; => #t\n(symbol? \"x\") ; => #f\n```"
},
{
"name": "tool?",
"module": "predicates",
"section": "LLM Type Predicates",
"summary": "Test if a value is a tool definition.",
"examples": [
"(deftool my-tool \"A test tool\" {:x {:type :string}} (lambda (x) x))\n(tool? my-tool) ; => #t\n(tool? 42) ; => #f"
],
"body": "Test if a value is a tool definition.\n\n```sema\n(deftool my-tool \"A test tool\" {:x {:type :string}} (lambda (x) x))\n(tool? my-tool) ; => #t\n(tool? 42) ; => #f\n```"
},
{
"name": "type-of",
"module": "predicates",
"section": "Type Predicates",
"summary": "Return the type of `x` as a keyword. Alias of `type`. For records, returns the record's type tag.",
"params": [
{
"name": "x",
"type": "any"
}
],
"returns": "keyword",
"examples": [
"(type-of 42) ; => :int\n(type-of \"hi\") ; => :string\n(type-of '(1 2)) ; => :list"
],
"body": "Return the type of `x` as a keyword. Alias of `type`. For records, returns the record's type tag.\n\n```sema\n(type-of 42) ; => :int\n(type-of \"hi\") ; => :string\n(type-of '(1 2)) ; => :list\n```"
},
{
"name": "vector?",
"module": "predicates",
"section": "Collection Predicates",
"summary": "Test if a value is a vector.",
"examples": [
"(vector? [1]) ; => #t\n(vector? '(1)) ; => #f"
],
"body": "Test if a value is a vector.\n\n```sema\n(vector? [1]) ; => #t\n(vector? '(1)) ; => #f\n```"
},
{
"name": "zero?",
"module": "predicates",
"section": "Numeric Predicates",
"summary": "Test if a number is zero.",
"examples": [
"(zero? 0) ; => #t\n(zero? 1) ; => #f"
],
"body": "Test if a number is zero.\n\n```sema\n(zero? 0) ; => #t\n(zero? 1) ; => #f\n```"
},
{
"name": "prompt/append",
"module": "prompt",
"summary": "Concatenate two or more prompts into a single prompt, preserving message order. Variadic; takes one or more prompt values.",
"params": [
{
"name": "prompts",
"type": "prompt"
}
],
"returns": "prompt",
"examples": [
"(prompt/append system-prompt user-prompt)"
],
"body": "Concatenate two or more prompts into a single prompt, preserving message order. Variadic; takes one or more prompt values.\n\n```sema\n(prompt/append system-prompt user-prompt)\n```"
},
{
"name": "prompt/concat",
"module": "prompt",
"summary": "Alias for `prompt/append`: concatenate one or more prompts into a single prompt, preserving message order.",
"params": [
{
"name": "prompts",
"type": "prompt"
}
],
"returns": "prompt",
"examples": [
"(prompt/concat intro-prompt body-prompt closing-prompt)"
],
"body": "Alias for `prompt/append`: concatenate one or more prompts into a single prompt, preserving message order.\n\n```sema\n(prompt/concat intro-prompt body-prompt closing-prompt)\n```"
},
{
"name": "prompt/fill",
"module": "prompt",
"summary": "Substitute `{{key}}` placeholders in every message of the prompt using the vars map (keys looked up as keywords). Slots with no matching key are left unchanged.",
"params": [
{
"name": "prompt",
"type": "prompt"
},
{
"name": "vars",
"type": "map"
}
],
"returns": "prompt",
"examples": [
"(prompt/fill template {:name \"Ada\" :topic \"loops\"})"
],
"body": "Substitute `{{key}}` placeholders in every message of the prompt using the vars map (keys looked up as keywords). Slots with no matching key are left unchanged.\n\n```sema\n(prompt/fill template {:name \"Ada\" :topic \"loops\"})\n```"
},
{
"name": "prompt/messages",
"module": "prompt",
"summary": "Return the prompt's messages as a list of message values.",
"params": [
{
"name": "prompt",
"type": "prompt"
}
],
"returns": "list",
"examples": [
"(prompt/messages my-prompt)"
],
"body": "Return the prompt's messages as a list of message values.\n\n```sema\n(prompt/messages my-prompt)\n```"
},
{
"name": "prompt/set-system",
"module": "prompt",
"summary": "Return a new prompt with its system message set to the given string. Any existing system messages are removed and the new one is placed first.",
"params": [
{
"name": "prompt",
"type": "prompt"
},
{
"name": "system",
"type": "string"
}
],
"returns": "prompt",
"examples": [
"(prompt/set-system my-prompt \"You are a careful code reviewer.\")"
],
"body": "Return a new prompt with its system message set to the given string. Any existing system messages are removed and the new one is placed first.\n\n```sema\n(prompt/set-system my-prompt \"You are a careful code reviewer.\")\n```"
},
{
"name": "prompt/slots",
"module": "prompt",
"summary": "Return the list of distinct `{{slot}}` placeholder names found across the prompt's messages, as keywords.",
"params": [
{
"name": "prompt",
"type": "prompt"
}
],
"returns": "list",
"examples": [
"(prompt/slots template) ; => (:name :topic)"
],
"body": "Return the list of distinct `{{slot}}` placeholder names found across the prompt's messages, as keywords.\n\n```sema\n(prompt/slots template) ; => (:name :topic)\n```"
},
{
"name": "define-record-type",
"module": "records",
"section": "Defining Record Types",
"summary": "Define a new record type, generating a constructor, predicate, and one accessor per field.",
"examples": [
"(define-record-type point\n (make-point x y) ; constructor (positional args)\n point? ; predicate\n (x point-x) ; (field-name accessor-name)\n (y point-y))",
"(define-record-type <type-name>\n (<constructor> <field-name> ...)\n <predicate>\n (<field-name> <accessor>) ...)"
],
"body": "Define a new record type, generating a constructor, predicate, and one accessor per field.\n\n```sema\n(define-record-type point\n (make-point x y) ; constructor (positional args)\n point? ; predicate\n (x point-x) ; (field-name accessor-name)\n (y point-y))\n```\n\nGeneral syntax:\n\n```sema\n(define-record-type <type-name>\n (<constructor> <field-name> ...)\n <predicate>\n (<field-name> <accessor>) ...)\n```"
},
{
"name": "record?",
"module": "records",
"section": "Introspection",
"summary": "Test if a value is any record instance (of any record type).",
"examples": [
"(record? (make-point 3 4)) ; => #t\n(record? {:x 3 :y 4}) ; => #f\n(record? 42) ; => #f"
],
"body": "Test if a value is any record instance (of any record type).\n\n```sema\n(record? (make-point 3 4)) ; => #t\n(record? {:x 3 :y 4}) ; => #f\n(record? 42) ; => #f\n```"
},
{
"name": "type",
"module": "records",
"section": "Introspection",
"summary": "Return the type of a value as a keyword. For records, returns the record's type name:",
"examples": [
"(type (make-point 3 4)) ; => :point\n(type [1 2 3]) ; => :vector\n(type {:a 1}) ; => :map"
],
"body": "Return the type of a value as a keyword. For records, returns the record's type name:\n\n```sema\n(type (make-point 3 4)) ; => :point\n(type [1 2 3]) ; => :vector\n(type {:a 1}) ; => :map\n```"
},
{
"name": "\"",
"module": "regex",
"section": "Escaping Guide",
"summary": "Inside `#\"...\"`, use `\\\"`:",
"examples": [
"(regex/match? #\"\\\"[^\\\"]+\\\"\" \"say \\\"hello\\\"\")\n; => #t"
],
"body": "Inside `#\"...\"`, use `\\\"`:\n\n```sema\n(regex/match? #\"\\\"[^\\\"]+\\\"\" \"say \\\"hello\\\"\")\n; => #t\n```"
},
{
"name": "regex/find-all",
"module": "regex",
"section": "Matching",
"summary": "Find all non-overlapping matches of a pattern.",
"examples": [
"(regex/find-all #\"\\d+\" \"a1b2c3\") ; => (\"1\" \"2\" \"3\")\n(regex/find-all #\"[A-Z]\" \"Hello World\") ; => (\"H\" \"W\")"
],
"body": "Find all non-overlapping matches of a pattern.\n\n```sema\n(regex/find-all #\"\\d+\" \"a1b2c3\") ; => (\"1\" \"2\" \"3\")\n(regex/find-all #\"[A-Z]\" \"Hello World\") ; => (\"H\" \"W\")\n```"
},
{
"name": "regex/match",
"module": "regex",
"section": "Matching",
"summary": "Match a pattern and return match details as a map, or `nil` if no match.",
"examples": [
"(regex/match #\"(\\d+)-(\\w+)\" \"item-42-foo\")\n; => {:match \"42-foo\" :groups (\"42\" \"foo\") :start 5 :end 11}\n\n(regex/match #\"xyz\" \"abc\")\n; => nil",
"(regex/match #\"(\\d+)(?:-(\\d+))?\" \"42\")\n; => {:match \"42\" :groups (\"42\" nil) :start 0 :end 2}"
],
"body": "Match a pattern and return match details as a map, or `nil` if no match.\n\n**Signature:** `(regex/match pattern text) → map | nil`\n\nThe returned map contains:\n\n| Key | Value |\n|-----|-------|\n| `:match` | The full matched substring |\n| `:groups` | List of capture groups (group 1, 2, …) |\n| `:start` | Start byte offset in the input |\n| `:end` | End byte offset in the input |\n\n```sema\n(regex/match #\"(\\d+)-(\\w+)\" \"item-42-foo\")\n; => {:match \"42-foo\" :groups (\"42\" \"foo\") :start 5 :end 11}\n\n(regex/match #\"xyz\" \"abc\")\n; => nil\n```\n\nOptional capture groups that don't participate in the match become `nil`:\n\n```sema\n(regex/match #\"(\\d+)(?:-(\\d+))?\" \"42\")\n; => {:match \"42\" :groups (\"42\" nil) :start 0 :end 2}\n```\n\n`:start` and `:end` are byte offsets (UTF-8). For ASCII text they match character indices, but for non-ASCII they may differ."
},
{
"name": "regex/match?",
"module": "regex",
"section": "Matching",
"summary": "Test if a pattern matches anywhere in a string. Returns `#t` or `#f`.",
"examples": [
"(regex/match? #\"\\d+\" \"abc123\") ; => #t\n(regex/match? #\"\\d+\" \"no digits\") ; => #f\n(regex/match? #\"^\\d+$\" \"abc123\") ; => #f (anchored — must match entire string)\n(regex/match? #\"^\\d+$\" \"123\") ; => #t"
],
"body": "Test if a pattern matches anywhere in a string. Returns `#t` or `#f`.\n\n```sema\n(regex/match? #\"\\d+\" \"abc123\") ; => #t\n(regex/match? #\"\\d+\" \"no digits\") ; => #f\n(regex/match? #\"^\\d+$\" \"abc123\") ; => #f (anchored — must match entire string)\n(regex/match? #\"^\\d+$\" \"123\") ; => #t\n```"
},
{
"name": "regex/replace",
"module": "regex",
"section": "Replacement",
"summary": "Replace the **first** match of a pattern.",
"examples": [
"(regex/replace #\"\\d+\" \"X\" \"a1b2c3\") ; => \"aXb2c3\"",
"(regex/replace #\"(\\d+)-(\\w+)\" \"$2:$1\" \"item-42-foo\")\n; => \"item-foo:42\"",
"(regex/replace #\"(?P<num>\\d+)-(?P<word>\\w+)\" \"$word:$num\" \"item-42-foo\")\n; => \"item-foo:42\""
],
"body": "Replace the **first** match of a pattern.\n\n**Signature:** `(regex/replace pattern replacement text) → string`\n\n```sema\n(regex/replace #\"\\d+\" \"X\" \"a1b2c3\") ; => \"aXb2c3\"\n```\n\nCapture group references (`$1`, `$2`, …) work in the replacement string:\n\n```sema\n(regex/replace #\"(\\d+)-(\\w+)\" \"$2:$1\" \"item-42-foo\")\n; => \"item-foo:42\"\n```\n\nNamed capture groups also work:\n\n```sema\n(regex/replace #\"(?P<num>\\d+)-(?P<word>\\w+)\" \"$word:$num\" \"item-42-foo\")\n; => \"item-foo:42\"\n```"
},
{
"name": "regex/replace-all",
"module": "regex",
"section": "Replacement",
"summary": "Replace **all** matches of a pattern.",
"examples": [
"(regex/replace-all #\"\\d\" \"X\" \"a1b2\") ; => \"aXbX\"\n(regex/replace-all #\"\\s+\" \" \" \"a b c\") ; => \"a b c\""
],
"body": "Replace **all** matches of a pattern.\n\n```sema\n(regex/replace-all #\"\\d\" \"X\" \"a1b2\") ; => \"aXbX\"\n(regex/replace-all #\"\\s+\" \" \" \"a b c\") ; => \"a b c\"\n```"
},
{
"name": "regex/split",
"module": "regex",
"section": "Splitting",
"summary": "Split a string by a regex delimiter.",
"examples": [
"(regex/split #\",\" \"a,b,c\") ; => (\"a\" \"b\" \"c\")\n(regex/split #\"\\s+\" \"hello world\") ; => (\"hello\" \"world\")\n(regex/split #\"[,;]\" \"a,b;c,d\") ; => (\"a\" \"b\" \"c\" \"d\")"
],
"body": "Split a string by a regex delimiter.\n\n```sema\n(regex/split #\",\" \"a,b,c\") ; => (\"a\" \"b\" \"c\")\n(regex/split #\"\\s+\" \"hello world\") ; => (\"hello\" \"world\")\n(regex/split #\"[,;]\" \"a,b;c,d\") ; => (\"a\" \"b\" \"c\" \"d\")\n```"
},
{
"name": "route/from-tools",
"module": "route",
"summary": "Generate HTTP routes from a list of tool definitions. For each tool it creates a `POST /tools/<name>` route whose handler runs the tool with the request's JSON body and returns the result as JSON, plus a `GET /tools/<name>/schema` route exposing the tool's name, description, and parameter schema. Canonical alias for `tools->routes`.",
"params": [
{
"name": "tools"
}
],
"returns": "list",
"examples": [
"(http/serve {:port 8080\n :routes (route/from-tools [get-weather search-docs])})"
],
"body": "Generate HTTP routes from a list of tool definitions. For each tool it creates a `POST /tools/<name>` route whose handler runs the tool with the request's JSON body and returns the result as JSON, plus a `GET /tools/<name>/schema` route exposing the tool's name, description, and parameter schema. Canonical alias for `tools->routes`.\n\n```sema\n(http/serve {:port 8080\n :routes (route/from-tools [get-weather search-docs])})\n```"
},
{
"name": "route/prefix",
"module": "route",
"summary": "Prepend a path prefix to every route's pattern in the given list, returning a new list of routes. Each route is a `[method pattern handler ...]` vector; a trailing slash on the prefix is trimmed.",
"params": [
{
"name": "prefix",
"type": "string"
},
{
"name": "routes"
}
],
"returns": "list",
"examples": [
"(route/prefix \"/api\"\n [[:get \"/users\" list-users]\n [:post \"/users\" create-user]])"
],
"body": "Prepend a path prefix to every route's pattern in the given list, returning a new list of routes. Each route is a `[method pattern handler ...]` vector; a trailing slash on the prefix is trimmed.\n\n```sema\n(route/prefix \"/api\"\n [[:get \"/users\" list-users]\n [:post \"/users\" create-user]])\n```"
},
{
"name": "serial/close",
"module": "serial",
"section": "Connection Lifecycle",
"summary": "Close the port and free the handle. Subsequent calls with that handle raise `invalid handle`.",
"examples": [
"(serial/close handle)"
],
"body": "```sema\n(serial/close handle)\n```\n\nClose the port and free the handle. Subsequent calls with that handle raise `invalid handle`."
},
{
"name": "serial/list",
"module": "serial",
"section": "Connection Lifecycle",
"summary": "List the available serial port device paths on the host.",
"examples": [
"(serial/list)\n;; macOS: (\"/dev/tty.usbmodem1201\" \"/dev/tty.Bluetooth-Incoming-Port\")\n;; Linux: (\"/dev/ttyUSB0\" \"/dev/ttyACM0\")"
],
"body": "List the available serial port device paths on the host.\n\n```sema\n(serial/list)\n;; macOS: (\"/dev/tty.usbmodem1201\" \"/dev/tty.Bluetooth-Incoming-Port\")\n;; Linux: (\"/dev/ttyUSB0\" \"/dev/ttyACM0\")\n```"
},
{
"name": "serial/open",
"module": "serial",
"section": "Connection Lifecycle",
"summary": "Open a serial port and return an integer **handle** used by every other function in this module. Raises an error if the device is busy or doesn't exist; the message includes the path and baud rate as a hint.",
"examples": [
"(serial/open path baud) ; default 2000 ms read timeout\n(serial/open path baud timeout-ms)",
"(define pico (serial/open \"/dev/tty.usbmodem1201\" 115200))\n(define modem (serial/open \"/dev/ttyUSB0\" 9600 5000)) ; 5s read timeout"
],
"body": "```sema\n(serial/open path baud) ; default 2000 ms read timeout\n(serial/open path baud timeout-ms)\n```\n\nOpen a serial port and return an integer **handle** used by every other function in this module. Raises an error if the device is busy or doesn't exist; the message includes the path and baud rate as a hint.\n\n```sema\n(define pico (serial/open \"/dev/tty.usbmodem1201\" 115200))\n(define modem (serial/open \"/dev/ttyUSB0\" 9600 5000)) ; 5s read timeout\n```"
},
{
"name": "serial/read-line",
"module": "serial",
"section": "I/O",
"summary": "Read until `\\n`, then trim trailing `\\r` / `\\n` and return the line. Blocks until either a newline arrives or the port's read timeout elapses (configured at `serial/open` time) — on timeout, raises an error.",
"examples": [
"(serial/read-line handle) → string",
"(serial/read-line pico) ; => \"ready\""
],
"body": "```sema\n(serial/read-line handle) → string\n```\n\nRead until `\\n`, then trim trailing `\\r` / `\\n` and return the line. Blocks until either a newline arrives or the port's read timeout elapses (configured at `serial/open` time) — on timeout, raises an error.\n\n```sema\n(serial/read-line pico) ; => \"ready\"\n```"
},
{
"name": "serial/send",
"module": "serial",
"section": "I/O",
"summary": "Convenience for line-oriented JSON protocols (such as the [sema-bridge](https://github.com/HelgeSverre/sema/tree/main/examples) firmware that ships with the Pico examples). Writes `command + \"\\n\"`, flushes, reads one line back, and parses it as JSON. Returns `nil` if the response line is empty.",
"examples": [
"(serial/send handle command) → parsed-json | nil",
"(serial/send pico \"{\\\"cmd\\\":\\\"led-on\\\",\\\"pin\\\":25}\")\n;; => {:ok #t}\n\n(serial/send pico \"{\\\"cmd\\\":\\\"adc-read\\\",\\\"pin\\\":26}\")\n;; => {:ok #t :value 2048}"
],
"body": "```sema\n(serial/send handle command) → parsed-json | nil\n```\n\nConvenience for line-oriented JSON protocols (such as the [sema-bridge](https://github.com/HelgeSverre/sema/tree/main/examples) firmware that ships with the Pico examples). Writes `command + \"\\n\"`, flushes, reads one line back, and parses it as JSON. Returns `nil` if the response line is empty.\n\n```sema\n(serial/send pico \"{\\\"cmd\\\":\\\"led-on\\\",\\\"pin\\\":25}\")\n;; => {:ok #t}\n\n(serial/send pico \"{\\\"cmd\\\":\\\"adc-read\\\",\\\"pin\\\":26}\")\n;; => {:ok #t :value 2048}\n```"
},
{
"name": "serial/write",
"module": "serial",
"section": "I/O",
"summary": "Write a raw string to the port and flush. No newline appended — append `\"\\n\"` yourself if your protocol expects it.",
"examples": [
"(serial/write handle string)",
"(serial/write modem \"AT\\r\\n\")"
],
"body": "```sema\n(serial/write handle string)\n```\n\nWrite a raw string to the port and flush. No newline appended — append `\"\\n\"` yourself if your protocol expects it.\n\n```sema\n(serial/write modem \"AT\\r\\n\")\n```"
},
{
"name": "->",
"module": "special-forms",
"summary": "`->` is the thread-first macro. It inserts `val` as the first argument of each successive `form`, creating a left-to-right pipeline. A bare symbol `f` is treated as `(f)`. The macro expands recursively at macro-expansion time into ordinary nested calls, so there is no runtime overhead. Thread-first is especially useful for data transformations, building nested structures, and drilling into maps or records, because most accessor and update functions take the data as their primary argument.",
"examples": [
"(-> 5 (+ 3) (* 2))\n;; => 16",
"(-> response :body json/decode :data :users)\n;; equivalent to (:users (:data (json/decode (:body response))))",
"(-> '(1 2 3 4)\n (filter odd?)\n (map (fn (x) (* x x))))\n;; => (1 9)"
],
"body": "`->` is the thread-first macro. It inserts `val` as the first argument of each successive `form`, creating a left-to-right pipeline. A bare symbol `f` is treated as `(f)`. The macro expands recursively at macro-expansion time into ordinary nested calls, so there is no runtime overhead. Thread-first is especially useful for data transformations, building nested structures, and drilling into maps or records, because most accessor and update functions take the data as their primary argument.\n\n```sema\n(-> 5 (+ 3) (* 2))\n;; => 16\n```\n\nYou can mix function calls and bare symbols. The following example drills into nested map keys after decoding JSON:\n\n```sema\n(-> response :body json/decode :data :users)\n;; equivalent to (:users (:data (json/decode (:body response))))\n```\n\nBecause the threaded value always appears first, `->` pairs naturally with collection utilities such as `map` and `filter`:\n\n```sema\n(-> '(1 2 3 4)\n (filter odd?)\n (map (fn (x) (* x x))))\n;; => (1 9)\n```\n\n**Note:** `->` is a prelude macro, so it is available automatically without an import.",
"syntax": "(-> val form ...)",
"special_form": true
},
{
"name": "->>",
"module": "special-forms",
"summary": "`->>` is the thread-last macro. It inserts `val` as the last argument of each successive `form`, creating a pipeline that flows through sequence-processing and higher-order functions. A bare symbol `f` is treated as `(f)`. Like `->`, it is a recursive prelude macro that expands into ordinary nested calls before evaluation or compilation, incurring no runtime cost. Thread-last is idiomatic for list comprehensions and collection pipelines where the collection is conventionally the final argument, such as with `map`, `filter`, `fold`, and `take`.",
"examples": [
"(->> (range 1 100)\n (filter even?)\n (map (fn (x) (* x x)))\n (take 5))\n;; => (4 16 36 64 100)",
"(->> '(1 2 3 4 5)\n (filter (fn (x) (> x 2)))\n (map (fn (x) (* x 10))))\n;; => (30 40 50)",
"(->> '(1 2 3)\n (map (fn (x) (* x x)))\n (as-> nums (cons 0 nums))\n (reverse))\n;; => (9 4 1 0)"
],
"body": "`->>` is the thread-last macro. It inserts `val` as the last argument of each successive `form`, creating a pipeline that flows through sequence-processing and higher-order functions. A bare symbol `f` is treated as `(f)`. Like `->`, it is a recursive prelude macro that expands into ordinary nested calls before evaluation or compilation, incurring no runtime cost. Thread-last is idiomatic for list comprehensions and collection pipelines where the collection is conventionally the final argument, such as with `map`, `filter`, `fold`, and `take`.\n\n```sema\n(->> (range 1 100)\n (filter even?)\n (map (fn (x) (* x x)))\n (take 5))\n;; => (4 16 36 64 100)\n```\n\nThread-last composes naturally with functions that accept callbacks or options before the primary collection:\n\n```sema\n(->> '(1 2 3 4 5)\n (filter (fn (x) (> x 2)))\n (map (fn (x) (* x 10))))\n;; => (30 40 50)\n```\n\nWhen a single step needs the value in a different position, interleave `as->` or switch to `->` for that step.\n\n```sema\n(->> '(1 2 3)\n (map (fn (x) (* x x)))\n (as-> nums (cons 0 nums))\n (reverse))\n;; => (9 4 1 0)\n```\n\n**Note:** `->>` is a prelude macro and requires no import.",
"syntax": "(->> val form ...)",
"special_form": true
},
{
"name": "and",
"module": "special-forms",
"summary": "Short-circuit logical AND. Evaluates expressions from left to right and returns the first falsy value it encounters. If all expressions are truthy, it returns the value of the last expression. With no arguments, `and` returns `#t`.",
"examples": [
"(and #t #t) ; => #t\n(and #t #f) ; => #f",
"(and \"hello\" 42 #t) ; => #t (all truthy, last value returned)",
"(define m {:a 1})\n(and m (:a m) (+ (:a m) 10)) ; => 11",
"(and) ; => #t\n(and #f (expensive)) ; => #f (expensive is never called)"
],
"body": "Short-circuit logical AND. Evaluates expressions from left to right and returns the first falsy value it encounters. If all expressions are truthy, it returns the value of the last expression. With no arguments, `and` returns `#t`.\n\nBecause it is a special form, `and` stops evaluating as soon as a falsy result is found; subsequent expressions are never touched. This makes it ideal for chained predicates, guarded property access, or pipelines where later steps depend on earlier ones succeeding. Only `nil` and `#f` are falsy in Sema.\n\n```sema\n(and #t #t) ; => #t\n(and #t #f) ; => #f\n```\n\n```sema\n(and \"hello\" 42 #t) ; => #t (all truthy, last value returned)\n```\n\n```sema\n(define m {:a 1})\n(and m (:a m) (+ (:a m) 10)) ; => 11\n```\n\n```sema\n(and) ; => #t\n(and #f (expensive)) ; => #f (expensive is never called)\n```",
"syntax": "(and expr ...)",
"special_form": true
},
{
"name": "as->",
"module": "special-forms",
"summary": "`as->` is the thread-as macro. It binds `val` to `name`, evaluates the first `form` with that binding, then rebinds `name` to the result and continues with the next form. This allows the threaded value to appear in any argument position, not just first or last. Implemented as a recursive prelude macro, `as->` expands into a sequence of `let` bindings, so each step runs in its own lexical scope with no runtime overhead. It is the escape hatch for pipelines where a single step does not fit the `->` or `->>` convention, or when the value must be used multiple times in one step.",
"examples": [
"(as-> 5 x (+ x 3) (* x x) (- x 1))\n;; => 63",
"(as-> \"hello\" s\n (string/upper s)\n (string/replace s \"L\" \"X\")\n (string/append s \"!\"))\n;; => \"HEXXO!\"",
"(-> '(1 2 3)\n (map (fn (x) (* x 2)))\n (as-> nums (zip '(a b c) nums)))\n;; => ((a 2) (b 4) (c 6))"
],
"body": "`as->` is the thread-as macro. It binds `val` to `name`, evaluates the first `form` with that binding, then rebinds `name` to the result and continues with the next form. This allows the threaded value to appear in any argument position, not just first or last. Implemented as a recursive prelude macro, `as->` expands into a sequence of `let` bindings, so each step runs in its own lexical scope with no runtime overhead. It is the escape hatch for pipelines where a single step does not fit the `->` or `->>` convention, or when the value must be used multiple times in one step.\n\n```sema\n(as-> 5 x (+ x 3) (* x x) (- x 1))\n;; => 63\n```\n\nBecause `name` is rebound after every step, you can reference the intermediate result arbitrarily:\n\n```sema\n(as-> \"hello\" s\n (string/upper s)\n (string/replace s \"L\" \"X\")\n (string/append s \"!\"))\n;; => \"HEXXO!\"\n```\n\n`as->` combines cleanly with `->` and `->>`. Use it to switch argument placement mid-pipeline:\n\n```sema\n(-> '(1 2 3)\n (map (fn (x) (* x 2)))\n (as-> nums (zip '(a b c) nums)))\n;; => ((a 2) (b 4) (c 6))\n```\n\n**Note:** `as->` is a prelude macro and is available automatically without an import.",
"syntax": "(as-> val name form ...)",
"special_form": true
},
{
"name": "async",
"module": "special-forms",
"summary": "Spawn the body expressions as an asynchronous task and return a promise for its result. The body is wrapped in a zero-argument thunk and handed to `async/spawn`; it runs cooperatively on the VM scheduler when driven by `await`, `async/run`, or `async/all`.",
"examples": [
"(define p (async (+ 1 2)))\n(await p) ; => 3",
"(let ((p1 (async (* 3 3)))\n (p2 (async (* 4 4))))\n (+ (await p1) (await p2))) ; => 25",
"(define slow (async\n (sleep 100)\n \"done\"))\n(await slow) ; => \"done\""
],
"body": "Spawn the body expressions as an asynchronous task and return a promise for its result. The body is wrapped in a zero-argument thunk and handed to `async/spawn`; it runs cooperatively on the VM scheduler when driven by `await`, `async/run`, or `async/all`.\n\n```sema\n(define p (async (+ 1 2)))\n(await p) ; => 3\n```\n\nMultiple async tasks can run concurrently. They interleave at yield points such as channel operations, `await`, or `sleep`.\n\n```sema\n(let ((p1 (async (* 3 3)))\n (p2 (async (* 4 4))))\n (+ (await p1) (await p2))) ; => 25\n```\n\nThe promise returned by `async` can be passed around, stored in data structures, or awaited multiple times. If the body completes normally, the promise resolves to the last expression's value. If the body throws an error, the promise rejects and the error is re-raised when the promise is awaited.\n\n```sema\n(define slow (async\n (sleep 100)\n \"done\"))\n(await slow) ; => \"done\"\n```",
"syntax": "(async body ...)",
"special_form": true
},
{
"name": "await",
"module": "special-forms",
"summary": "Wait for a promise to settle and return its resolved value. If the promise was rejected, the rejection reason is re-raised as an error. Inside an async task, `await` yields control to the scheduler, allowing other tasks to run until the promise resolves. At the top level, `await` drives the scheduler inline until the promise settles.",
"examples": [
"(await (async (* 6 7))) ; => 42",
"(let ((p1 (async (* 3 3)))\n (p2 (async (* 4 4))))\n (+ (await p1) (await p2))) ; => 25",
"(try\n (await (async (throw \"oops\")))\n (catch e\n (println \"Caught:\" (:message e))))"
],
"body": "Wait for a promise to settle and return its resolved value. If the promise was rejected, the rejection reason is re-raised as an error. Inside an async task, `await` yields control to the scheduler, allowing other tasks to run until the promise resolves. At the top level, `await` drives the scheduler inline until the promise settles.\n\n```sema\n(await (async (* 6 7))) ; => 42\n```\n\nAwaiting multiple promises in sequence lets you compose async results. Tasks that are not currently awaited continue to make progress in the background.\n\n```sema\n(let ((p1 (async (* 3 3)))\n (p2 (async (* 4 4))))\n (+ (await p1) (await p2))) ; => 25\n```\n\nIf a task throws an error, `await` re-raises it. You can wrap `await` in `try` to handle rejections gracefully.\n\n```sema\n(try\n (await (async (throw \"oops\")))\n (catch e\n (println \"Caught:\" (:message e))))\n```\n\n**Note:** `await` lowers to a call to `async/await`.",
"syntax": "(await promise)",
"special_form": true
},
{
"name": "begin",
"module": "special-forms",
"summary": "Sequencing special form that evaluates each expression in order and returns the value of the last one. All intermediate results are discarded, so `begin` is primarily used for side effects — printing, mutating state, or performing I/O — grouped into a single expression.",
"examples": [
"(begin\n (println \"step 1\")\n (println \"step 2\")\n (+ 1 2)) ; => 3",
"(define x 0)\n(begin\n (set! x 1)\n (set! x 2)\n x) ; => 2",
"(begin) ; => nil",
"(if (> n 0)\n (begin\n (println \"positive\")\n (process n))\n (begin\n (println \"non-positive\")\n (skip n)))"
],
"body": "Sequencing special form that evaluates each expression in order and returns the value of the last one. All intermediate results are discarded, so `begin` is primarily used for side effects — printing, mutating state, or performing I/O — grouped into a single expression.\n\nWith no arguments, `begin` returns `nil`. The last expression is evaluated in tail position, which means tail-call optimization applies. `progn` is accepted as an alias for `begin` for compatibility with Common Lisp conventions.\n\n```sema\n(begin\n (println \"step 1\")\n (println \"step 2\")\n (+ 1 2)) ; => 3\n```\n\n```sema\n(define x 0)\n(begin\n (set! x 1)\n (set! x 2)\n x) ; => 2\n```\n\n```sema\n(begin) ; => nil\n```\n\n```sema\n(if (> n 0)\n (begin\n (println \"positive\")\n (process n))\n (begin\n (println \"non-positive\")\n (skip n)))\n```",
"syntax": "(begin body ...)",
"special_form": true
},
{
"name": "case",
"module": "special-forms",
"summary": "Dispatch on a value by comparing it against literal datums. `case` evaluates `expr` once, then checks each clause in order. The first clause whose datum list contains the value is selected, and its body is evaluated. An optional `else` clause at the end acts as a catch-all. If no clause matches and there is no `else`, `case` returns `nil`.",
"examples": [
"(case (:status response)\n ((:ok) \"success\")\n ((:error :timeout) \"failure\")\n (else \"unknown\"))",
"(case n\n ((1) \"one\")\n ((2 3) \"two or three\")\n (else \"other\"))",
"(case '(:a 1)\n ((:a) \"alpha\")\n ((:b) \"beta\")\n (else \"unknown\"))\n;; => \"alpha\""
],
"body": "Dispatch on a value by comparing it against literal datums. `case` evaluates `expr` once, then checks each clause in order. The first clause whose datum list contains the value is selected, and its body is evaluated. An optional `else` clause at the end acts as a catch-all. If no clause matches and there is no `else`, `case` returns `nil`.\n\nUnlike `cond`, which evaluates arbitrary test expressions, `case` uses equality comparison against literal values. This makes it ideal for switching on keywords, numbers, or other constants. The datums in each clause are grouped in a sublist, so a single clause can match multiple values.\n\n```sema\n(case (:status response)\n ((:ok) \"success\")\n ((:error :timeout) \"failure\")\n (else \"unknown\"))\n```\n\n```sema\n(case n\n ((1) \"one\")\n ((2 3) \"two or three\")\n (else \"other\"))\n```\n\n```sema\n(case '(:a 1)\n ((:a) \"alpha\")\n ((:b) \"beta\")\n (else \"unknown\"))\n;; => \"alpha\"\n```\n\n**Note:** The VM backend lowers `case` to a `let` binding for the key followed by nested `if` expressions, avoiding repeated evaluation of the discriminant.",
"syntax": "(case expr ((datum ...) body ...) ... [(else body ...)])",
"special_form": true
},
{
"name": "cond",
"module": "special-forms",
"summary": "Multi-branch conditional that evaluates tests in order until one is truthy, then evaluates the corresponding body and returns the last body's value. Each clause is a list where the first element is a test expression and the remaining elements are body expressions. The `else` clause, if present, acts as a catch-all and must be the last clause.",
"examples": [
"(cond\n ((< x 0) \"negative\")\n ((= x 0) \"zero\")\n (else \"positive\"))",
"(define score 85)\n(cond\n ((>= score 90) \"A\")\n ((>= score 80) \"B\")\n ((>= score 70) \"C\")\n (else \"F\"))",
"(cond\n ((> 5 10) \"unreachable\")\n ((< 3 7))) ; => #t (test-only clause)",
"(cond\n ((= 1 2) \"no\")\n ((= 3 4) \"also no\")) ; => nil (no match, no else)"
],
"body": "Multi-branch conditional that evaluates tests in order until one is truthy, then evaluates the corresponding body and returns the last body's value. Each clause is a list where the first element is a test expression and the remaining elements are body expressions. The `else` clause, if present, acts as a catch-all and must be the last clause.\n\nIf a clause contains only a test with no body (e.g., `(predicate)`), `cond` returns `#t` when that test is truthy. If no clause matches and there is no `else`, `cond` returns `nil`. Like `if`, `cond` is a special form and evaluates only the selected branch.\n\n```sema\n(cond\n ((< x 0) \"negative\")\n ((= x 0) \"zero\")\n (else \"positive\"))\n```\n\n```sema\n(define score 85)\n(cond\n ((>= score 90) \"A\")\n ((>= score 80) \"B\")\n ((>= score 70) \"C\")\n (else \"F\"))\n```\n\n```sema\n(cond\n ((> 5 10) \"unreachable\")\n ((< 3 7))) ; => #t (test-only clause)\n```\n\n```sema\n(cond\n ((= 1 2) \"no\")\n ((= 3 4) \"also no\")) ; => nil (no match, no else)\n```",
"syntax": "(cond (test body ...) ... [(else body ...)])",
"special_form": true
},
{
"name": "def",
"module": "special-forms",
"summary": "Bind a value to a name in the current environment. `def` is a silent alias for `define`, provided for compatibility with other Lisp dialects (Clojure, Scheme, etc.). It behaves identically to `define`: it creates a top-level binding, and if the name already exists, the previous value is overwritten.",
"examples": [
"(def x 42)\nx ; => 42",
"(def square (fn (x) (* x x)))\n(square 7) ; => 49",
"(def [a b c] '(1 2 3))\n(+ a b c) ; => 6\n\n(def {:keys [host port]} {:host \"localhost\" :port 8080})\nhost ; => \"localhost\"\nport ; => 8080"
],
"body": "Bind a value to a name in the current environment. `def` is a silent alias for `define`, provided for compatibility with other Lisp dialects (Clojure, Scheme, etc.). It behaves identically to `define`: it creates a top-level binding, and if the name already exists, the previous value is overwritten.\n\nLike `define`, `def` supports destructuring patterns in the binding position. You can destructure vectors with `[a b c]` or maps with `{:keys [name age]}` to extract values directly.\n\n```sema\n(def x 42)\nx ; => 42\n```\n\nDefining a function with `def` using `fn`:\n\n```sema\n(def square (fn (x) (* x x)))\n(square 7) ; => 49\n```\n\nDestructuring with `def`:\n\n```sema\n(def [a b c] '(1 2 3))\n(+ a b c) ; => 6\n\n(def {:keys [host port]} {:host \"localhost\" :port 8080})\nhost ; => \"localhost\"\nport ; => 8080\n```\n\n**Note:** `def` does not create local bindings — for that, use `let`, `let*`, or `letrec`.",
"syntax": "(def name value)",
"special_form": true
},
{
"name": "defagent",
"module": "special-forms",
"summary": "Define an LLM agent. The `name` must be a symbol. The second argument is an options map that configures the agent. The `:system` key provides the system prompt string that sets the agent's behavior. The `:tools` key is a vector or list of tool values created with `deftool`, which the agent can invoke during a conversation. The `:model` key specifies which LLM model to use (e.g., `\"claude-sonnet\"` or `\"gpt-4\"`). The `:max-turns` key controls the maximum number of agent-tool interaction rounds and defaults to `10`.",
"examples": [
"(defagent greeter\n {:system \"You are a friendly greeter. Keep responses brief.\"\n :model \"claude-sonnet\"\n :max-turns 5})",
"(deftool get-weather\n \"Get the current weather for a location.\"\n {:location {:type :string :description \"City name\"}}\n (lambda (location)\n (format \"Sunny and 22°C in ~a\" location)))\n\n(defagent weather-bot\n {:system \"You are a weather assistant.\"\n :tools [get-weather]\n :max-turns 10})",
"(agent/name greeter) ; => \"greeter\"\n(agent/system greeter) ; => \"You are a friendly greeter...\"\n(agent/max-turns greeter) ; => 5\n(agent? greeter) ; => #t"
],
"body": "Define an LLM agent. The `name` must be a symbol. The second argument is an options map that configures the agent. The `:system` key provides the system prompt string that sets the agent's behavior. The `:tools` key is a vector or list of tool values created with `deftool`, which the agent can invoke during a conversation. The `:model` key specifies which LLM model to use (e.g., `\"claude-sonnet\"` or `\"gpt-4\"`). The `:max-turns` key controls the maximum number of agent-tool interaction rounds and defaults to `10`.\n\nThe agent value is bound to `name` in the current environment and is also returned by the form. You can inspect an agent with accessor functions: `agent/name`, `agent/system`, `agent/tools`, `agent/model`, and `agent/max-turns`. Use `agent?` to check if a value is an agent.\n\n```sema\n(defagent greeter\n {:system \"You are a friendly greeter. Keep responses brief.\"\n :model \"claude-sonnet\"\n :max-turns 5})\n```\n\nDefining an agent with tools:\n\n```sema\n(deftool get-weather\n \"Get the current weather for a location.\"\n {:location {:type :string :description \"City name\"}}\n (lambda (location)\n (format \"Sunny and 22°C in ~a\" location)))\n\n(defagent weather-bot\n {:system \"You are a weather assistant.\"\n :tools [get-weather]\n :max-turns 10})\n```\n\nInspecting an agent:\n\n```sema\n(agent/name greeter) ; => \"greeter\"\n(agent/system greeter) ; => \"You are a friendly greeter...\"\n(agent/max-turns greeter) ; => 5\n(agent? greeter) ; => #t\n```",
"syntax": "(defagent name {:system \"...\" :tools [...] :model \"...\" :max-turns N})",
"special_form": true
},
{
"name": "define",
"module": "special-forms",
"summary": "Bind a value, function, or destructuring pattern in the current environment. `define` is the primary way to introduce names at the top level or inside a local scope. It always returns `nil`.",
"examples": [
"(define x 42)\nx ; => 42",
"(define (square n) (* n n))\n(square 7) ; => 49",
"(define [a b c] '(1 2 3))\n(+ a b c) ; => 6",
"(define {:keys [host port]} {:host \"localhost\" :port 8080})\nhost ; => \"localhost\"\nport ; => 8080"
],
"body": "Bind a value, function, or destructuring pattern in the current environment. `define` is the primary way to introduce names at the top level or inside a local scope. It always returns `nil`.\n\nWhen the first argument is a symbol, the second argument is evaluated and bound to that name. When the first argument is a list starting with a symbol, `define` treats it as a function shorthand: `(define (f x) body)` is equivalent to `(define f (lambda (x) body))`. When the first argument is a vector or map pattern, the value is destructured and multiple bindings are created at once.\n\nIn interactive mode, redefining a builtin native function prints a warning to stderr.\n\n```sema\n(define x 42)\nx ; => 42\n```\n\n```sema\n(define (square n) (* n n))\n(square 7) ; => 49\n```\n\n```sema\n(define [a b c] '(1 2 3))\n(+ a b c) ; => 6\n```\n\n```sema\n(define {:keys [host port]} {:host \"localhost\" :port 8080})\nhost ; => \"localhost\"\nport ; => 8080\n```",
"syntax": "(define name value) | (define (name params ...) body ...) | (define pattern value)",
"special_form": true
},
{
"name": "defmacro",
"module": "special-forms",
"summary": "Define a compile-time macro. Macros receive their arguments unevaluated as Sema data structures and must return a new form that the evaluator then expands and evaluates. `defmacro` takes a name symbol, a parameter list, and one or more body expressions.",
"examples": [
"(defmacro unless (test body)\n `(if (not ,test) ,body))\n(unless (> 3 5) \"it is false\") ; => \"it is false\"",
"(defmacro when-let (binding then)\n `(let (,binding)\n (when ,(car binding) ,then)))\n(when-let (x 42) (* x 2)) ; => 84",
"(defmacro my-or (a b)\n `(let ((temp ,a))\n (if temp temp ,b)))\n(my-or #f 99) ; => 99"
],
"body": "Define a compile-time macro. Macros receive their arguments unevaluated as Sema data structures and must return a new form that the evaluator then expands and evaluates. `defmacro` takes a name symbol, a parameter list, and one or more body expressions.\n\nUnlike `lambda`, macros do not capture a closure environment. Macro expansion uses the environment active at the call site. Parameter lists support rest arguments via dot notation. The body is typically written with quasiquote (`` ` ``) to construct the output form conveniently, using `,expr` to unquote evaluated subexpressions and `,@expr` for unquote-splicing.\n\n`defmacro` always returns `nil`.\n\n```sema\n(defmacro unless (test body)\n `(if (not ,test) ,body))\n(unless (> 3 5) \"it is false\") ; => \"it is false\"\n```\n\n```sema\n(defmacro when-let (binding then)\n `(let (,binding)\n (when ,(car binding) ,then)))\n(when-let (x 42) (* x 2)) ; => 84\n```\n\n```sema\n(defmacro my-or (a b)\n `(let ((temp ,a))\n (if temp temp ,b)))\n(my-or #f 99) ; => 99\n```",
"syntax": "(defmacro name (params ...) body ...)",
"special_form": true
},
{
"name": "defmethod",
"module": "special-forms",
"summary": "Register a method implementation on an existing multimethod defined with `defmulti`. When the multimethod is called and its dispatch function returns a value equal to `dispatch-value`, the `handler-fn` is invoked with the original arguments. The `multi-name` must be a symbol naming an already-defined multimethod.",
"examples": [
"(defmulti area (fn (shape) (get shape :type)))\n(defmethod area :circle (fn (s) (* 3 (get s :radius) (get s :radius))))\n(defmethod area :rect (fn (s) (* (get s :width) (get s :height))))\n(defmethod area :default (fn (s) 0))\n\n(area {:type :circle :radius 5}) ; => 75\n(area {:type :square}) ; => 0 (default method)",
"(defmulti greet (fn (x) (get x :lang)))\n(defmethod greet :en (fn (x) \"hello\"))\n\n(greet {:lang :en}) ; => \"hello\"\n\n(defmethod greet :fr (fn (x) \"bonjour\"))\n(greet {:lang :fr}) ; => \"bonjour\"",
"(defmulti fizzbuzz (fn (n)\n (cond ((= (modulo n 15) 0) :fizzbuzz)\n ((= (modulo n 3) 0) :fizz)\n ((= (modulo n 5) 0) :buzz)\n (#t :num))))\n\n(defmethod fizzbuzz :fizzbuzz (fn (n) \"FizzBuzz\"))\n(defmethod fizzbuzz :fizz (fn (n) \"Fizz\"))\n(defmethod fizzbuzz :buzz (fn (n) \"Buzz\"))\n(defmethod fizzbuzz :num (fn (n) n))\n\n(fizzbuzz 15) ; => \"FizzBuzz\"\n(fizzbuzz 7) ; => 7"
],
"body": "Register a method implementation on an existing multimethod defined with `defmulti`. When the multimethod is called and its dispatch function returns a value equal to `dispatch-value`, the `handler-fn` is invoked with the original arguments. The `multi-name` must be a symbol naming an already-defined multimethod.\n\nUse the special dispatch value `:default` to register a fallback handler that runs when no other method matches. Methods can be added at any time after the multimethod is created, including conditionally or from other modules, making the system open for extension.\n\n`defmethod` returns `nil`.\n\n```sema\n(defmulti area (fn (shape) (get shape :type)))\n(defmethod area :circle (fn (s) (* 3 (get s :radius) (get s :radius))))\n(defmethod area :rect (fn (s) (* (get s :width) (get s :height))))\n(defmethod area :default (fn (s) 0))\n\n(area {:type :circle :radius 5}) ; => 75\n(area {:type :square}) ; => 0 (default method)\n```\n\nAdding methods dynamically after the multimethod is already in use:\n\n```sema\n(defmulti greet (fn (x) (get x :lang)))\n(defmethod greet :en (fn (x) \"hello\"))\n\n(greet {:lang :en}) ; => \"hello\"\n\n(defmethod greet :fr (fn (x) \"bonjour\"))\n(greet {:lang :fr}) ; => \"bonjour\"\n```\n\nDispatch on integer values:\n\n```sema\n(defmulti fizzbuzz (fn (n)\n (cond ((= (modulo n 15) 0) :fizzbuzz)\n ((= (modulo n 3) 0) :fizz)\n ((= (modulo n 5) 0) :buzz)\n (#t :num))))\n\n(defmethod fizzbuzz :fizzbuzz (fn (n) \"FizzBuzz\"))\n(defmethod fizzbuzz :fizz (fn (n) \"Fizz\"))\n(defmethod fizzbuzz :buzz (fn (n) \"Buzz\"))\n(defmethod fizzbuzz :num (fn (n) n))\n\n(fizzbuzz 15) ; => \"FizzBuzz\"\n(fizzbuzz 7) ; => 7\n```",
"syntax": "(defmethod multi-name dispatch-value handler-fn)",
"special_form": true
},
{
"name": "defmulti",
"module": "special-forms",
"summary": "Define a multimethod — a function whose implementation is selected at runtime by applying a dispatch function to the arguments. The `name` must be a symbol. The `dispatch-fn` is a regular function that receives the same arguments as the multimethod and returns a dispatch value. Methods are registered separately with `defmethod`, associating a dispatch value with a handler function.",
"examples": [
"(defmulti area (fn (shape) (get shape :type)))\n(defmethod area :circle (fn (s) (* 3.14159 (expt (get s :radius) 2))))\n(defmethod area :rect (fn (s) (* (get s :width) (get s :height))))\n\n(area {:type :circle :radius 5}) ; => 78.53975\n(area {:type :rect :width 3 :height 4}) ; => 12",
"(defmulti describe (fn (x) (type x)))\n(defmethod describe :int (fn (x) \"integer\"))\n(defmethod describe :string (fn (x) \"text\"))\n(defmethod describe :list (fn (x) \"list\"))\n\n(list (describe 42) (describe \"hi\") (describe '(1 2)))\n;; => (\"integer\" \"text\" \"list\")",
"(defmulti combine (fn (a b) (list (type a) (type b))))\n(defmethod combine '(:int :int) (fn (a b) (+ a b)))\n(defmethod combine '(:string :string) (fn (a b) (string-append a b)))\n\n(combine 1 2) ; => 3\n(combine \"a\" \"b\") ; => \"ab\""
],
"body": "Define a multimethod — a function whose implementation is selected at runtime by applying a dispatch function to the arguments. The `name` must be a symbol. The `dispatch-fn` is a regular function that receives the same arguments as the multimethod and returns a dispatch value. Methods are registered separately with `defmethod`, associating a dispatch value with a handler function.\n\nMultimethods are open for extension: new methods can be added after the multimethod is defined, even from other modules. If no method matches the dispatch value and no `:default` method is registered, an error is raised. Dispatch values can be keywords, strings, numbers, lists, or any comparable value.\n\n```sema\n(defmulti area (fn (shape) (get shape :type)))\n(defmethod area :circle (fn (s) (* 3.14159 (expt (get s :radius) 2))))\n(defmethod area :rect (fn (s) (* (get s :width) (get s :height))))\n\n(area {:type :circle :radius 5}) ; => 78.53975\n(area {:type :rect :width 3 :height 4}) ; => 12\n```\n\nDispatch on runtime type:\n\n```sema\n(defmulti describe (fn (x) (type x)))\n(defmethod describe :int (fn (x) \"integer\"))\n(defmethod describe :string (fn (x) \"text\"))\n(defmethod describe :list (fn (x) \"list\"))\n\n(list (describe 42) (describe \"hi\") (describe '(1 2)))\n;; => (\"integer\" \"text\" \"list\")\n```\n\nMulti-argument dispatch:\n\n```sema\n(defmulti combine (fn (a b) (list (type a) (type b))))\n(defmethod combine '(:int :int) (fn (a b) (+ a b)))\n(defmethod combine '(:string :string) (fn (a b) (string-append a b)))\n\n(combine 1 2) ; => 3\n(combine \"a\" \"b\") ; => \"ab\"\n```",
"syntax": "(defmulti name dispatch-fn)",
"special_form": true
},
{
"name": "defn",
"module": "special-forms",
"summary": "Define a named function. `defn` is an alias for `defun` and behaves identically. It expands into `(define (name params ...) body ...)`, taking a name symbol, a parameter list, and one or more body expressions.",
"examples": [
"(defn add (a b)\n (+ a b))\n(add 3 4) ; => 7",
"(defn sum-pair [[a b]]\n (+ a b))\n(sum-pair '(3 4)) ; => 7",
"(defn greet {:keys [name title]}\n (format \"Hello ~a ~a\" title name))\n(greet {:name \"Smith\" :title \"Dr.\"}) ; => \"Hello Dr. Smith\""
],
"body": "Define a named function. `defn` is an alias for `defun` and behaves identically. It expands into `(define (name params ...) body ...)`, taking a name symbol, a parameter list, and one or more body expressions.\n\nParameter lists support rest arguments via dot notation `(x . rest)` and destructuring patterns in parameter positions. The last body expression is evaluated in tail position. `defn` always returns `nil`.\n\nUse whichever spelling matches your preference or codebase conventions. The evaluator recognizes both `defun` and `defn` as the same special form.\n\n```sema\n(defn add (a b)\n (+ a b))\n(add 3 4) ; => 7\n```\n\n```sema\n(defn sum-pair [[a b]]\n (+ a b))\n(sum-pair '(3 4)) ; => 7\n```\n\n```sema\n(defn greet {:keys [name title]}\n (format \"Hello ~a ~a\" title name))\n(greet {:name \"Smith\" :title \"Dr.\"}) ; => \"Hello Dr. Smith\"\n```",
"syntax": "(defn name (params ...) body ...)",
"special_form": true
},
{
"name": "deftool",
"module": "special-forms",
"summary": "Define a tool that can be invoked by an LLM agent. The `name` must be a symbol. The `description` is a human-readable string explaining what the tool does — the LLM uses this to decide when to call the tool. The `parameters-map` describes the tool's arguments using a JSON Schema-like structure; each key is a parameter name mapping to a map with `:type`, `:description`, and optionally other schema fields. The `handler-expr` is evaluated to produce a function that receives the tool arguments and returns a result.",
"examples": [
"(deftool add-numbers\n \"Add two numbers together.\"\n {:a {:type :number :description \"First number\"}\n :b {:type :number :description \"Second number\"}}\n (lambda (a b) (+ a b)))",
"(deftool greet-person\n \"Greet someone by name.\"\n {:name {:type :string :description \"The person's name\"}}\n (lambda (name)\n (string-append \"Hello, \" name \"!\")))",
"(tool/name add-numbers) ; => \"add-numbers\"\n(tool/description add-numbers) ; => \"Add two numbers together.\"\n(map? (tool/parameters add-numbers)) ; => #t\n(tool? add-numbers) ; => #t",
"(defagent calculator\n {:system \"You help with math.\"\n :tools [add-numbers]\n :max-turns 5})"
],
"body": "Define a tool that can be invoked by an LLM agent. The `name` must be a symbol. The `description` is a human-readable string explaining what the tool does — the LLM uses this to decide when to call the tool. The `parameters-map` describes the tool's arguments using a JSON Schema-like structure; each key is a parameter name mapping to a map with `:type`, `:description`, and optionally other schema fields. The `handler-expr` is evaluated to produce a function that receives the tool arguments and returns a result.\n\nThe tool value is bound to `name` in the current environment and is also returned by the form. You can inspect a tool with `tool/name`, `tool/description`, `tool/parameters`, and test values with `tool?`. Tools are passed to agents via the `:tools` key in `defagent`.\n\n```sema\n(deftool add-numbers\n \"Add two numbers together.\"\n {:a {:type :number :description \"First number\"}\n :b {:type :number :description \"Second number\"}}\n (lambda (a b) (+ a b)))\n```\n\nA tool that works with a single map argument (common pattern for flexible schemas):\n\n```sema\n(deftool greet-person\n \"Greet someone by name.\"\n {:name {:type :string :description \"The person's name\"}}\n (lambda (name)\n (string-append \"Hello, \" name \"!\")))\n```\n\nInspecting a tool:\n\n```sema\n(tool/name add-numbers) ; => \"add-numbers\"\n(tool/description add-numbers) ; => \"Add two numbers together.\"\n(map? (tool/parameters add-numbers)) ; => #t\n(tool? add-numbers) ; => #t\n```\n\nUsing a tool with an agent:\n\n```sema\n(defagent calculator\n {:system \"You help with math.\"\n :tools [add-numbers]\n :max-turns 5})\n```",
"syntax": "(deftool name \"description\" parameters-map handler-expr)",
"special_form": true
},
{
"name": "defun",
"module": "special-forms",
"summary": "Define a named function. `defun` is syntactic sugar that expands into `(define (name params ...) body ...)`. It takes a function name symbol, a parameter list, and one or more body expressions. The last body expression is evaluated in tail position.",
"examples": [
"(defun greet (name)\n (string-append \"Hello, \" name))\n(greet \"world\") ; => \"Hello, world\"",
"(defun sum-list (xs)\n (if (null? xs)\n 0\n (+ (car xs) (sum-list (cdr xs)))))\n(sum-list '(1 2 3)) ; => 6",
"(defun greet-many (greeting . names)\n (map (fn (n) (string-append greeting \" \" n)) names))\n(greet-many \"Hi\" \"Ada\" \"Bob\") ; => (\"Hi Ada\" \"Hi Bob\")"
],
"body": "Define a named function. `defun` is syntactic sugar that expands into `(define (name params ...) body ...)`. It takes a function name symbol, a parameter list, and one or more body expressions. The last body expression is evaluated in tail position.\n\nParameter lists support rest arguments via dot notation: `(defun f (x . rest) rest)`. Destructuring patterns are also supported in parameter positions. `defun` always returns `nil`.\n\nThe alias `defn` is accepted as an alternative spelling (Clojure-style). Both forms are handled identically by the evaluator.\n\n```sema\n(defun greet (name)\n (string-append \"Hello, \" name))\n(greet \"world\") ; => \"Hello, world\"\n```\n\n```sema\n(defun sum-list (xs)\n (if (null? xs)\n 0\n (+ (car xs) (sum-list (cdr xs)))))\n(sum-list '(1 2 3)) ; => 6\n```\n\n```sema\n(defun greet-many (greeting . names)\n (map (fn (n) (string-append greeting \" \" n)) names))\n(greet-many \"Hi\" \"Ada\" \"Bob\") ; => (\"Hi Ada\" \"Hi Bob\")\n```",
"syntax": "(defun name (params ...) body ...)",
"special_form": true
},
{
"name": "delay",
"module": "special-forms",
"summary": "Create a lazy promise. `delay` captures its body expression without evaluating it, returning a promise object (also called a thunk) that can be forced later. This is useful for deferred or expensive computations that may never be needed, or for building lazy streams and infinite sequences.",
"examples": [
"(define p (delay (+ 1 2)))\np ; => a promise object\n(force p) ; => 3",
"(define counter 0)\n(define p (delay (begin (set! counter (+ counter 1)) counter)))\n(force p) ; => 1\n(force p) ; => 1 (memoized, body not re-run)\ncounter ; => 1",
"(promise? (delay 42)) ; => #t\n(promise? 42) ; => #f",
"(define p (delay 99))\n(promise-forced? p) ; => #f\n(force p)\n(promise-forced? p) ; => #t"
],
"body": "Create a lazy promise. `delay` captures its body expression without evaluating it, returning a promise object (also called a thunk) that can be forced later. This is useful for deferred or expensive computations that may never be needed, or for building lazy streams and infinite sequences.\n\nInternally, `delay` creates a parameterless closure that captures the current environment. The body is evaluated only when the promise is passed to `force`, and the result is memoized so subsequent calls to `force` return the cached value without re-executing the body. Use `promise?` to test whether a value is a promise, and `promise-forced?` to check whether it has already been evaluated.\n\n```sema\n(define p (delay (+ 1 2)))\np ; => a promise object\n(force p) ; => 3\n```\n\n```sema\n(define counter 0)\n(define p (delay (begin (set! counter (+ counter 1)) counter)))\n(force p) ; => 1\n(force p) ; => 1 (memoized, body not re-run)\ncounter ; => 1\n```\n\n```sema\n(promise? (delay 42)) ; => #t\n(promise? 42) ; => #f\n```\n\n```sema\n(define p (delay 99))\n(promise-forced? p) ; => #f\n(force p)\n(promise-forced? p) ; => #t\n```",
"syntax": "(delay expr)",
"special_form": true
},
{
"name": "do",
"module": "special-forms",
"summary": "Scheme-style iterative loop with explicit variable bindings, step expressions, and a termination test. Each binding specifies a variable name, an initial value, and an optional step expression. On each iteration, all variables are updated in parallel using their step expressions (or retain their current value if no step is given). The loop terminates when the test expression evaluates to truthy, at which point the result expressions are evaluated and the last result is returned. If no result expressions are provided, the loop returns `nil`.",
"examples": [
"(do ((i 0 (+ i 1))\n (sum 0 (+ sum i)))\n ((= i 10) sum))\n;; => 45",
"(do ((i 0 (+ i 1)))\n ((= i 5))\n (println i))\n;; prints 0 through 4, returns nil",
"(do ((n 10 (/ n 2)))\n ((= n 0) \"done\")\n (println n))\n;; prints 10, 5, 2, 1"
],
"body": "Scheme-style iterative loop with explicit variable bindings, step expressions, and a termination test. Each binding specifies a variable name, an initial value, and an optional step expression. On each iteration, all variables are updated in parallel using their step expressions (or retain their current value if no step is given). The loop terminates when the test expression evaluates to truthy, at which point the result expressions are evaluated and the last result is returned. If no result expressions are provided, the loop returns `nil`.\n\nThe body expressions, if any, are evaluated on every iteration before the step update and are typically used for side effects. `do` is useful for numeric iteration, accumulation, and any loop that requires parallel variable updates. For sequential binding semantics, use `let*` inside the loop body instead.\n\n```sema\n(do ((i 0 (+ i 1))\n (sum 0 (+ sum i)))\n ((= i 10) sum))\n;; => 45\n```\n\n```sema\n(do ((i 0 (+ i 1)))\n ((= i 5))\n (println i))\n;; prints 0 through 4, returns nil\n```\n\n```sema\n(do ((n 10 (/ n 2)))\n ((= n 0) \"done\")\n (println n))\n;; prints 10, 5, 2, 1\n```\n\n**Note:** The VM compiles `do` to a dedicated `DoLoop` IR node with parallel step assignment.",
"syntax": "(do ((var init [step]) ...) (test [result ...]) body ...)",
"special_form": true
},
{
"name": "dotimes",
"module": "special-forms",
"summary": "`dotimes` evaluates `body` exactly `count` times, binding `var` to the integers `0` through `count - 1` on successive iterations. It is implemented as a prelude macro that expands into a `do` loop, so it executes in constant stack space and benefits from tail-call optimization. The return value is `nil` because the generated loop has no result expression after its termination test. `dotimes` is the idiomatic choice for side-effecting loops that only need a counter, such as repeating an action, printing output, or indexing into a fixed-size structure.",
"examples": [
"(dotimes (i 3)\n (println i))\n;; prints 0, 1, 2",
"(let ((total 0))\n (dotimes (i 4)\n (set! total (+ total i)))\n total)\n;; => 6"
],
"body": "`dotimes` evaluates `body` exactly `count` times, binding `var` to the integers `0` through `count - 1` on successive iterations. It is implemented as a prelude macro that expands into a `do` loop, so it executes in constant stack space and benefits from tail-call optimization. The return value is `nil` because the generated loop has no result expression after its termination test. `dotimes` is the idiomatic choice for side-effecting loops that only need a counter, such as repeating an action, printing output, or indexing into a fixed-size structure.\n\n```sema\n(dotimes (i 3)\n (println i))\n;; prints 0, 1, 2\n```\n\nIf you need to accumulate a result, mutate a variable in the enclosing scope:\n\n```sema\n(let ((total 0))\n (dotimes (i 4)\n (set! total (+ total i)))\n total)\n;; => 6\n```\n\nWhen `count` is zero or negative, the body never executes.",
"syntax": "(dotimes (var count) body ...)",
"special_form": true
},
{
"name": "eval",
"module": "special-forms",
"summary": "Evaluate a data structure as code at runtime. `eval` takes a single expression, evaluates it to obtain a Sema value (typically a quoted form or the result of `read`), then evaluates that value as Sema code in the current environment.",
"examples": [
"(eval '(+ 1 2)) ; => 3",
"(eval (read \"(* 3 4)\")) ; => 12",
"(define x 10)\n(eval '(set! x 20))\nx ; => 20",
"(define ops '(+ - *))\n(eval (list (car ops) 3 4)) ; => 7"
],
"body": "Evaluate a data structure as code at runtime. `eval` takes a single expression, evaluates it to obtain a Sema value (typically a quoted form or the result of `read`), then evaluates that value as Sema code in the current environment.\n\nThis is the core primitive for metaprogramming. It allows programs to construct or read code dynamically and then execute it. Because the evaluated code runs in the caller's environment, it can access and mutate existing bindings. `eval` is used internally by the REPL and by tools that transform and execute code at runtime.\n\n```sema\n(eval '(+ 1 2)) ; => 3\n```\n\n```sema\n(eval (read \"(* 3 4)\")) ; => 12\n```\n\n```sema\n(define x 10)\n(eval '(set! x 20))\nx ; => 20\n```\n\n```sema\n(define ops '(+ - *))\n(eval (list (car ops) 3 4)) ; => 7\n```\n\n**Note:** `eval` takes exactly one argument. Passing zero or more than one argument raises an arity error. The expression is evaluated in the current dynamic environment, so closures and module state are respected.",
"syntax": "(eval expr)",
"special_form": true
},
{
"name": "export",
"module": "special-forms",
"summary": "Declare which symbols a module makes available to other files that import it. `export` is not a standalone special form — it only appears as the second argument inside a `module` form, specifying the public interface of that module.",
"examples": [
";; math-utils.sema\n(module math-utils\n (export square cube)\n (define (square x) (* x x))\n (define (cube x) (* x x x))\n (define (internal-helper x) x)) ; not exported",
";; main.sema\n(import \"math-utils.sema\")\n(square 5) ; => 25\n(cube 3) ; => 27\n;; (internal-helper 1) ; error: unbound variable",
";; Selective export with selective import\n(module strings\n (export trim split)\n (define (trim s) ...)\n (define (split s sep) ...)\n (define (internal-escape s) ...)) ; private"
],
"body": "Declare which symbols a module makes available to other files that import it. `export` is not a standalone special form — it only appears as the second argument inside a `module` form, specifying the public interface of that module.\n\nBindings listed in `export` become visible to importers. Any top-level definitions in the module body that are not exported remain private and cannot be accessed from outside the module. This is the primary mechanism for encapsulation in Sema's module system.\n\nIf a module body does not contain an `export` form (or `module` is not used at all), all bindings are exported by default. However, best practice is to explicitly declare exports so the module's public API is clear and accidental leakage of internal helpers is prevented.\n\n```sema\n;; math-utils.sema\n(module math-utils\n (export square cube)\n (define (square x) (* x x))\n (define (cube x) (* x x x))\n (define (internal-helper x) x)) ; not exported\n```\n\n```sema\n;; main.sema\n(import \"math-utils.sema\")\n(square 5) ; => 25\n(cube 3) ; => 27\n;; (internal-helper 1) ; error: unbound variable\n```\n\n```sema\n;; Selective export with selective import\n(module strings\n (export trim split)\n (define (trim s) ...)\n (define (split s sep) ...)\n (define (internal-escape s) ...)) ; private\n```\n\n**Note:** `export` takes one or more bare symbols. It has no effect outside of a `module` form. See `import` for how to consume exported bindings, and `module` for the full module declaration syntax.",
"syntax": "(export sym1 sym2 ...)",
"special_form": true
},
{
"name": "fn",
"module": "special-forms",
"summary": "Create an anonymous function. `fn` is an alias for `lambda` and behaves identically. It takes a parameter list (or a single rest-parameter symbol) and one or more body expressions, returning a callable function value that closes over its defining environment.",
"examples": [
"((fn (x) (* x x)) 4) ; => 16",
"(map (fn (n) (+ n 1)) '(1 2 3)) ; => (2 3 4)",
"((fn (first . rest) rest) 10 20 30) ; => (20 30)",
"((fn {:keys [x y]} (+ x y)) {:x 1 :y 2}) ; => 3"
],
"body": "Create an anonymous function. `fn` is an alias for `lambda` and behaves identically. It takes a parameter list (or a single rest-parameter symbol) and one or more body expressions, returning a callable function value that closes over its defining environment.\n\nParameters may be supplied as a list or vector of symbols. Rest arguments use dot notation `(x . rest)`. Destructuring patterns in parameter positions are automatically desugared into an internal `let*`. A single symbol as the parameter spec captures all arguments as a list.\n\nUse `fn` or `lambda` according to your preferred style; both are recognized as the same special form by the evaluator.\n\n```sema\n((fn (x) (* x x)) 4) ; => 16\n```\n\n```sema\n(map (fn (n) (+ n 1)) '(1 2 3)) ; => (2 3 4)\n```\n\n```sema\n((fn (first . rest) rest) 10 20 30) ; => (20 30)\n```\n\n```sema\n((fn {:keys [x y]} (+ x y)) {:x 1 :y 2}) ; => 3\n```",
"syntax": "(fn (params ...) body ...) | (fn params body ...)",
"special_form": true
},
{
"name": "for",
"module": "special-forms",
"summary": "Iterate over one or more sequences for side effects. Each binding pairs a variable with an expression that evaluates to a sequence (list, vector, or range). The body is evaluated once for every combination of elements across all sequences, with the variables bound in a fresh lexical scope. `for` returns `nil`.",
"examples": [
"(for ((x (range 5)))\n (println x))\n;; prints 0 through 4",
"(for ((x (list 1 2 3))\n (y (list 10 20)))\n (println (list x y)))\n;; prints (1 10), (1 20), (2 10), (2 20), (3 10), (3 20)",
"(let ((total 0))\n (for ((n (list 10 20 30)))\n (set! total (+ total n)))\n total)\n;; => 60"
],
"body": "Iterate over one or more sequences for side effects. Each binding pairs a variable with an expression that evaluates to a sequence (list, vector, or range). The body is evaluated once for every combination of elements across all sequences, with the variables bound in a fresh lexical scope. `for` returns `nil`.\n\nMultiple bindings produce nested iteration (cartesian product), with the rightmost binding changing fastest. This form is similar to `for-each` but uses inline `let`-style bindings rather than a separate lambda. It is commonly used for printing, mutating external state, or performing I/O over collections.\n\n```sema\n(for ((x (range 5)))\n (println x))\n;; prints 0 through 4\n```\n\n```sema\n(for ((x (list 1 2 3))\n (y (list 10 20)))\n (println (list x y)))\n;; prints (1 10), (1 20), (2 10), (2 20), (3 10), (3 20)\n```\n\n```sema\n(let ((total 0))\n (for ((n (list 10 20 30)))\n (set! total (+ total n)))\n total)\n;; => 60\n```\n\n**Note:** `for` is typically provided as a macro that expands into `map` and sequence combinators. The variables are locally scoped and are not visible outside the form.",
"syntax": "(for ((var expr) ...) body ...)",
"special_form": true
},
{
"name": "for-range",
"module": "special-forms",
"summary": "`for-range` executes `body` repeatedly with `var` bound to successive integers starting at `start` (inclusive) and continuing while strictly less than `end` (exclusive). An optional fourth element in the binding vector sets the step size, which defaults to `1`. The macro expands into a `do` loop with parallel stepping, so every iteration runs in constant stack space thanks to tail-call optimization in the VM. Use `for-range` for counted loops, index-based iteration over arrays, or any situation where you need a numeric counter.",
"examples": [
"(for-range (i 0 5)\n (println i))\n;; prints 0 1 2 3 4",
"(for-range (i 0 10 2)\n (println i))\n;; prints 0 2 4 6 8"
],
"body": "`for-range` executes `body` repeatedly with `var` bound to successive integers starting at `start` (inclusive) and continuing while strictly less than `end` (exclusive). An optional fourth element in the binding vector sets the step size, which defaults to `1`. The macro expands into a `do` loop with parallel stepping, so every iteration runs in constant stack space thanks to tail-call optimization in the VM. Use `for-range` for counted loops, index-based iteration over arrays, or any situation where you need a numeric counter.\n\n```sema\n(for-range (i 0 5)\n (println i))\n;; prints 0 1 2 3 4\n```\n\nYou can specify a custom step to skip elements:\n\n```sema\n(for-range (i 0 10 2)\n (println i))\n;; prints 0 2 4 6 8\n```\n\n**Note:** The step must evaluate to a positive number. `for-range` uses a single `>=` termination test, so backward iteration (negative step) is not supported by this macro; use a named `let` or `do` loop for decrementing ranges.",
"syntax": "(for-range (var start end [step]) body ...)",
"special_form": true
},
{
"name": "for/filter",
"module": "special-forms",
"summary": "`for/filter` is a list-comprehension form that iterates over a sequence, binds each element to `var`, and evaluates `body` as a predicate. It returns a list containing every element for which the predicate is truthy, preserving the original order. When multiple binding pairs are provided, the sequences are traversed in parallel and the loop stops when the shortest sequence is exhausted. This form is convenient when you need to extract a subset of a collection without writing an explicit `filter` pipeline.",
"examples": [
"(for/filter ((x (range 10)))\n (even? x))\n;; => (0 2 4 6 8)",
"(define (positive? n) (> n 0))\n(for/filter ((x '(-3 -1 0 2 4)))\n (positive? x))\n;; => (2 4)",
"(for/filter ((x '(1 3 5)))\n (even? x))\n;; => ()"
],
"body": "`for/filter` is a list-comprehension form that iterates over a sequence, binds each element to `var`, and evaluates `body` as a predicate. It returns a list containing every element for which the predicate is truthy, preserving the original order. When multiple binding pairs are provided, the sequences are traversed in parallel and the loop stops when the shortest sequence is exhausted. This form is convenient when you need to extract a subset of a collection without writing an explicit `filter` pipeline.\n\n```sema\n(for/filter ((x (range 10)))\n (even? x))\n;; => (0 2 4 6 8)\n```\n\nYou can use any expression as the predicate, including calls to user-defined functions:\n\n```sema\n(define (positive? n) (> n 0))\n(for/filter ((x '(-3 -1 0 2 4)))\n (positive? x))\n;; => (2 4)\n```\n\nIf no element satisfies the predicate, `for/filter` returns the empty list.\n\n```sema\n(for/filter ((x '(1 3 5)))\n (even? x))\n;; => ()\n```",
"syntax": "(for/filter ((var sequence) ...) body)",
"special_form": true
},
{
"name": "for/fold",
"module": "special-forms",
"summary": "`for/fold` threads an accumulator through a sequence. On the first iteration each `acc` is bound to its `init` value; on each subsequent iteration `acc` is updated to the result of `body`. When the sequence is exhausted, the final accumulator value (or values, when multiple accumulators are provided) is returned. This form is ideal for aggregating data—summing numbers, building strings, counting matches, or reducing a collection to a single result—without writing an explicit recursive loop.",
"examples": [
"(for/fold ((sum 0))\n ((x (range 5)))\n (+ sum x))\n;; => 10",
"(for/fold ((acc '()))\n ((x '(1 2 3 4)))\n (cons x acc))\n;; => (4 3 2 1)",
"(for/fold ((total 0) (count 0))\n ((x '(1 2 3 4 5)))\n (values (+ total x) (+ count 1)))\n;; total => 15, count => 5"
],
"body": "`for/fold` threads an accumulator through a sequence. On the first iteration each `acc` is bound to its `init` value; on each subsequent iteration `acc` is updated to the result of `body`. When the sequence is exhausted, the final accumulator value (or values, when multiple accumulators are provided) is returned. This form is ideal for aggregating data—summing numbers, building strings, counting matches, or reducing a collection to a single result—without writing an explicit recursive loop.\n\n```sema\n(for/fold ((sum 0))\n ((x (range 5)))\n (+ sum x))\n;; => 10\n```\n\nThe accumulator can start as any type. For example, you can build a reversed list by consing each element onto the accumulator:\n\n```sema\n(for/fold ((acc '()))\n ((x '(1 2 3 4)))\n (cons x acc))\n;; => (4 3 2 1)\n```\n\nMultiple accumulator bindings let you compute several aggregated values in a single pass:\n\n```sema\n(for/fold ((total 0) (count 0))\n ((x '(1 2 3 4 5)))\n (values (+ total x) (+ count 1)))\n;; total => 15, count => 5\n```",
"syntax": "(for/fold ((acc init) ...) ((var sequence) ...) body)",
"special_form": true
},
{
"name": "for/list",
"module": "special-forms",
"summary": "List comprehension: evaluate the body for each combination of elements across the given sequences and collect the results into a list. Each binding pairs a variable with a sequence expression. Multiple bindings produce nested iteration (cartesian product), with the rightmost binding changing fastest. The body should produce a single value for each iteration.",
"examples": [
"(for/list ((x (range 5)))\n (* x x))\n;; => (0 1 4 9 16)",
"(for/list ((x (range 1 21))\n (:when (even? x)))\n (* x x))\n;; => (4 16 36 64 100 144 196 256 324 400)",
"(for/list ((x (list 1 2 3))\n (y (list 10 20)))\n (+ x y))\n;; => (11 12 21 22 31 32)"
],
"body": "List comprehension: evaluate the body for each combination of elements across the given sequences and collect the results into a list. Each binding pairs a variable with a sequence expression. Multiple bindings produce nested iteration (cartesian product), with the rightmost binding changing fastest. The body should produce a single value for each iteration.\n\n`for/list` supports `:when` filter clauses interleaved with bindings. A `:when` clause evaluates a predicate expression; if it is falsy, that iteration is skipped and no value is collected. This allows concise filtered comprehensions without separate `filter` calls.\n\n```sema\n(for/list ((x (range 5)))\n (* x x))\n;; => (0 1 4 9 16)\n```\n\n```sema\n(for/list ((x (range 1 21))\n (:when (even? x)))\n (* x x))\n;; => (4 16 36 64 100 144 196 256 324 400)\n```\n\n```sema\n(for/list ((x (list 1 2 3))\n (y (list 10 20)))\n (+ x y))\n;; => (11 12 21 22 31 32)\n```\n\n**Note:** `for/list` is typically implemented as a macro that expands to nested `map` and `append` calls. It is not a core special form and may need to be defined or imported from a comprehension library.",
"syntax": "(for/list ((var expr) ...) body)",
"special_form": true
},
{
"name": "for/map",
"module": "special-forms",
"summary": "Map comprehension: evaluate the body for each combination of elements across the given sequences and collect the results into a hash map. The body must produce a key-value pair, typically using `values` or `list`. Each binding pairs a variable with a sequence expression; multiple bindings produce nested iteration (cartesian product).",
"examples": [
"(for/map ((x '(1 2 3)))\n (values x (* x x)))\n;; => {1 1, 2 4, 3 9}",
"(for/map ((c \"abc\"))\n (values (str c) (string/to-keyword (str c))))\n;; => {\"a\" :a, \"b\" :b, \"c\" :c}",
"(for/map ((n (range 1 11))\n (:when (odd? n)))\n (values n (* n n)))\n;; => {1 1, 3 9, 5 25, 7 49, 9 81}"
],
"body": "Map comprehension: evaluate the body for each combination of elements across the given sequences and collect the results into a hash map. The body must produce a key-value pair, typically using `values` or `list`. Each binding pairs a variable with a sequence expression; multiple bindings produce nested iteration (cartesian product).\n\nLike `for/list`, `for/map` supports `:when` filter clauses to skip iterations conditionally. If multiple iterations produce the same key, later values overwrite earlier ones because the result is a hash map. This form is ideal for building lookup tables, inverted indexes, or any derived mapping from one or more input sequences.\n\n```sema\n(for/map ((x '(1 2 3)))\n (values x (* x x)))\n;; => {1 1, 2 4, 3 9}\n```\n\n```sema\n(for/map ((c \"abc\"))\n (values (str c) (string/to-keyword (str c))))\n;; => {\"a\" :a, \"b\" :b, \"c\" :c}\n```\n\n```sema\n(for/map ((n (range 1 11))\n (:when (odd? n)))\n (values n (* n n)))\n;; => {1 1, 3 9, 5 25, 7 49, 9 81}\n```\n\n**Note:** `for/map` is typically implemented as a macro that expands to `for/list` followed by `foldl` with `assoc`. It is not a core special form and may need to be defined or imported from a comprehension library.",
"syntax": "(for/map ((var expr) ...) body)",
"special_form": true
},
{
"name": "force",
"module": "special-forms",
"summary": "Evaluate a delayed promise created by `delay` and return its value. If the promise has already been forced, `force` returns the cached (memoized) result without re-evaluating the body. This makes `force` safe to call multiple times on the same promise.",
"examples": [
"(define p (delay (+ 1 2)))\n(force p) ; => 3\n(force p) ; => 3 (cached)",
"(define counter 0)\n(define p (delay (begin (set! counter (+ counter 1)) counter)))\n(force p) ; => 1\n(force p) ; => 1 (body only ran once)",
"(force 42) ; => error: expected thunk, got int\n(force \"hello\") ; => error: expected thunk, got string"
],
"body": "Evaluate a delayed promise created by `delay` and return its value. If the promise has already been forced, `force` returns the cached (memoized) result without re-evaluating the body. This makes `force` safe to call multiple times on the same promise.\n\n`force` evaluates its argument first, then checks whether the result is a promise. If so, it evaluates the promise's body in the captured environment, stores the result, and returns it. If the argument is not a promise, a type error is raised. This strict behavior prevents silently passing non-promise values through, which helps catch bugs early.\n\n```sema\n(define p (delay (+ 1 2)))\n(force p) ; => 3\n(force p) ; => 3 (cached)\n```\n\n```sema\n(define counter 0)\n(define p (delay (begin (set! counter (+ counter 1)) counter)))\n(force p) ; => 1\n(force p) ; => 1 (body only ran once)\n```\n\n```sema\n(force 42) ; => error: expected thunk, got int\n(force \"hello\") ; => error: expected thunk, got string\n```\n\n**Note:** Use `promise?` to test whether a value is a promise before calling `force`, and `promise-forced?` to check whether a promise has already been evaluated. These predicates are available in the standard library.",
"syntax": "(force promise)",
"special_form": true
},
{
"name": "if",
"module": "special-forms",
"summary": "Two-branch conditional special form. Evaluates `condition`, and if it is truthy evaluates `then-expr`; otherwise evaluates `else-expr`. Only the selected branch is evaluated — the other is never touched. In Sema, only `nil` and `#f` are falsy; every other value (including `0`, empty strings, and empty lists) is truthy.",
"examples": [
"(if (> x 0) \"positive\" \"non-positive\")",
"(if (empty? items)\n (println \"nothing to do\")\n (process items))",
"(define y 10)\n(if (= y 0) \"zero\") ; => nil (no else branch)",
"(if \"hello\" 1 2) ; => 1 (non-empty string is truthy)\n(if 0 1 2) ; => 1 (zero is truthy)\n(if nil 1 2) ; => 2 (nil is falsy)"
],
"body": "Two-branch conditional special form. Evaluates `condition`, and if it is truthy evaluates `then-expr`; otherwise evaluates `else-expr`. Only the selected branch is evaluated — the other is never touched. In Sema, only `nil` and `#f` are falsy; every other value (including `0`, empty strings, and empty lists) is truthy.\n\nThe `else-expr` is optional. When omitted and the condition is falsy, `if` returns `nil`. Because `if` is a special form and not a function, it does not evaluate its arguments eagerly; this makes it suitable for guarding side effects or expensive computations.\n\n```sema\n(if (> x 0) \"positive\" \"non-positive\")\n```\n\n```sema\n(if (empty? items)\n (println \"nothing to do\")\n (process items))\n```\n\n```sema\n(define y 10)\n(if (= y 0) \"zero\") ; => nil (no else branch)\n```\n\n```sema\n(if \"hello\" 1 2) ; => 1 (non-empty string is truthy)\n(if 0 1 2) ; => 1 (zero is truthy)\n(if nil 1 2) ; => 2 (nil is falsy)\n```",
"syntax": "(if condition then-expr else-expr)",
"special_form": true
},
{
"name": "if-let",
"module": "special-forms",
"summary": "`if-let` is a built-in macro that evaluates `expr`, binds the result to `var`, and chooses a branch based on whether the value is `nil`. If the value is non-nil, `then` is evaluated with `var` in scope; otherwise `else` is evaluated. The binding is only visible inside the `then` and `else` expressions.",
"examples": [
"(if-let (x (get {:a 1} :a)) x \"missing\")\n;; => 1",
"(if-let (x (get {:a 1} :b)) x \"missing\")\n;; => \"missing\"",
"(if-let (user (db/find-user id))\n (greet user)\n \"guest\")\n;; => greets the user, or falls back to \"guest\""
],
"body": "`if-let` is a built-in macro that evaluates `expr`, binds the result to `var`, and chooses a branch based on whether the value is `nil`. If the value is non-nil, `then` is evaluated with `var` in scope; otherwise `else` is evaluated. The binding is only visible inside the `then` and `else` expressions.\n\nThis form is useful when you want to both test for the presence of a value and use it, without repeating the lookup or computation.\n\n```sema\n(if-let (x (get {:a 1} :a)) x \"missing\")\n;; => 1\n```\n\n```sema\n(if-let (x (get {:a 1} :b)) x \"missing\")\n;; => \"missing\"\n```\n\n```sema\n(if-let (user (db/find-user id))\n (greet user)\n \"guest\")\n;; => greets the user, or falls back to \"guest\"\n```\n\n**Note:** `if-let` expands to a `let` and an `if`. It is defined in the prelude and does not require an import.",
"syntax": "(if-let (var expr) then else)",
"special_form": true
},
{
"name": "import",
"module": "special-forms",
"summary": "Load a module from a file or package and make its exported bindings available in the current environment. Unlike `load`, `import` uses the module system: only bindings declared with `export` inside a `module` form become visible. Non-exported bindings remain private.",
"examples": [
";; Import all exports from a local module\n(import \"math-utils.sema\")\n(square 5) ; => 25",
";; Selective import: only bring in specific symbols\n(import \"math-utils.sema\" square cube)",
";; Import from a package\n(import \"github.com/user/repo\")\n(greet \"world\") ; => \"hello world\""
],
"body": "Load a module from a file or package and make its exported bindings available in the current environment. Unlike `load`, `import` uses the module system: only bindings declared with `export` inside a `module` form become visible. Non-exported bindings remain private.\n\nThe first argument is evaluated to a string path. It may be a relative path (resolved against the current file's directory), an absolute path, or a package identifier such as `github.com/user/repo`. The evaluator checks the virtual file system first (used by bundled executables), then falls back to the real filesystem.\n\nSelective import is supported by listing bare symbols after the path. Only the named symbols are imported; if any symbol is not exported by the module, an error is raised. If no symbols are listed, all exported bindings are imported.\n\nModules are cached after the first load, so importing the same module multiple times in a single session is fast and idempotent. Cyclic imports are detected and return an error instead of causing infinite recursion.\n\n```sema\n;; Import all exports from a local module\n(import \"math-utils.sema\")\n(square 5) ; => 25\n```\n\n```sema\n;; Selective import: only bring in specific symbols\n(import \"math-utils.sema\" square cube)\n```\n\n```sema\n;; Import from a package\n(import \"github.com/user/repo\")\n(greet \"world\") ; => \"hello world\"\n```\n\n**Note:** `import` requires at least one argument (the path). The sandbox may restrict file-system access for imports. Use `load` instead if you simply want to execute a file in the current scope without module privacy boundaries.",
"syntax": "(import \"path\" sym1 sym2 ...)",
"special_form": true
},
{
"name": "lambda",
"module": "special-forms",
"summary": "Create an anonymous function. `lambda` takes a parameter list (or a single rest-parameter symbol) and one or more body expressions, returning a callable function value that closes over the environment where it was defined.",
"examples": [
"((lambda (x y) (+ x y)) 3 4) ; => 7",
"(define square (lambda (x) (* x x)))\n(square 5) ; => 25",
"((lambda (x . rest) rest) 1 2 3) ; => (2 3)",
"((lambda [a b] (+ a b)) '(1 2)) ; => 3"
],
"body": "Create an anonymous function. `lambda` takes a parameter list (or a single rest-parameter symbol) and one or more body expressions, returning a callable function value that closes over the environment where it was defined.\n\nThe parameter list may be a list or vector of symbols. Rest arguments are supported with dot notation: `(lambda (x . rest) rest)`. Destructuring patterns in parameter positions are automatically desugared into a `let*` inside the function body. When given a single symbol instead of a list, that symbol captures all arguments as a list.\n\nThe alias `fn` is accepted as an alternative spelling (Clojure-style). Both are handled identically by the evaluator.\n\n```sema\n((lambda (x y) (+ x y)) 3 4) ; => 7\n```\n\n```sema\n(define square (lambda (x) (* x x)))\n(square 5) ; => 25\n```\n\n```sema\n((lambda (x . rest) rest) 1 2 3) ; => (2 3)\n```\n\n```sema\n((lambda [a b] (+ a b)) '(1 2)) ; => 3\n```",
"syntax": "(lambda (params ...) body ...) | (lambda params body ...)",
"special_form": true
},
{
"name": "let",
"module": "special-forms",
"summary": "`let` binds local variables in parallel. All init expressions are evaluated in the outer environment before any bindings are created, so later bindings cannot refer to earlier ones. The result is the value of the last body expression.",
"examples": [
"(let ((x 10) (y 20))\n (+ x y))\n;; => 30",
"(let (([a b c] '(1 2 3)))\n (+ a b c))\n;; => 6",
"(let loop ((i 0) (sum 0))\n (if (= i 100)\n sum\n (loop (+ i 1) (+ sum i))))\n;; => 4950"
],
"body": "`let` binds local variables in parallel. All init expressions are evaluated in the outer environment before any bindings are created, so later bindings cannot refer to earlier ones. The result is the value of the last body expression.\n\n`let` supports destructuring patterns in binding positions. You can use vector patterns like `[a b]` or map patterns like `{:keys [name]}` to extract values directly from the bound expression.\n\n`let` also supports a named-let form for tail-recursive looping. When the first argument is a symbol instead of a bindings list, it names the loop and creates a self-referential function that can be called with new arguments.\n\n```sema\n(let ((x 10) (y 20))\n (+ x y))\n;; => 30\n```\n\n```sema\n(let (([a b c] '(1 2 3)))\n (+ a b c))\n;; => 6\n```\n\n```sema\n(let loop ((i 0) (sum 0))\n (if (= i 100)\n sum\n (loop (+ i 1) (+ sum i))))\n;; => 4950\n```\n\n**Note:** Named `let` requires at least three arguments: the loop name, the bindings list, and one or more body expressions.",
"syntax": "(let ((name value) ...) body ...)",
"special_form": true
},
{
"name": "let*",
"module": "special-forms",
"summary": "`let*` creates local bindings sequentially. Each init expression is evaluated in an environment that already contains the previous bindings, so later bindings can refer to earlier ones. The result is the value of the last body expression.",
"examples": [
"(let* ((x 10) (y (* x 2)))\n (+ x y))\n;; => 30",
"(let* (({:keys [name age]} {:name \"Ada\" :age 36})\n (greeting (format \"Hello, ~a\" name)))\n greeting)\n;; => \"Hello, Ada\"",
"(let* ((base 2)\n (exp 8)\n (result (expt base exp)))\n result)\n;; => 256"
],
"body": "`let*` creates local bindings sequentially. Each init expression is evaluated in an environment that already contains the previous bindings, so later bindings can refer to earlier ones. The result is the value of the last body expression.\n\nLike `let`, `let*` supports destructuring with vector and map patterns. It is useful when a computation builds on values computed just before it, avoiding the need for deeply nested `let` forms.\n\n```sema\n(let* ((x 10) (y (* x 2)))\n (+ x y))\n;; => 30\n```\n\n```sema\n(let* (({:keys [name age]} {:name \"Ada\" :age 36})\n (greeting (format \"Hello, ~a\" name)))\n greeting)\n;; => \"Hello, Ada\"\n```\n\n```sema\n(let* ((base 2)\n (exp 8)\n (result (expt base exp)))\n result)\n;; => 256\n```",
"syntax": "(let* ((name value) ...) body ...)",
"special_form": true
},
{
"name": "letrec",
"module": "special-forms",
"summary": "`letrec` binds local variables with mutual recursion. All names are created as placeholders first, then every init expression is evaluated in an environment where all names are already visible. This makes it possible for bindings to refer to each other, which is especially useful for mutually recursive functions.",
"examples": [
"(letrec ((even? (fn (n) (if (= n 0) #t (odd? (- n 1)))))\n (odd? (fn (n) (if (= n 0) #f (even? (- n 1))))))\n (even? 10))\n;; => #t",
"(letrec ((fact (fn (n) (if (= n 0) 1 (* n (fact (- n 1)))))))\n (fact 5))\n;; => 120",
"(letrec ((x y)\n (y 10))\n x)\n;; => nil"
],
"body": "`letrec` binds local variables with mutual recursion. All names are created as placeholders first, then every init expression is evaluated in an environment where all names are already visible. This makes it possible for bindings to refer to each other, which is especially useful for mutually recursive functions.\n\n`letrec` also supports destructuring patterns in binding positions. Because placeholders are initialized to `nil`, non-function init expressions that read other bindings will see the placeholder `nil`, not the final value. For this reason, `letrec` is most commonly used with function definitions.\n\nThe result is the value of the last body expression.\n\n```sema\n(letrec ((even? (fn (n) (if (= n 0) #t (odd? (- n 1)))))\n (odd? (fn (n) (if (= n 0) #f (even? (- n 1))))))\n (even? 10))\n;; => #t\n```\n\n```sema\n(letrec ((fact (fn (n) (if (= n 0) 1 (* n (fact (- n 1)))))))\n (fact 5))\n;; => 120\n```\n\n**Caution:** Reading another `letrec` binding before its init has finished will yield `nil`:\n\n```sema\n(letrec ((x y)\n (y 10))\n x)\n;; => nil\n```",
"syntax": "(letrec ((name value) ...) body ...)",
"special_form": true
},
{
"name": "load",
"module": "special-forms",
"summary": "Load and evaluate a Sema source file in the current environment. The file is read, parsed, and each top-level expression is evaluated sequentially. The last expression's value is returned. Unlike `import`, `load` does not use the module system — all top-level definitions from the loaded file become available directly in the current scope.",
"examples": [
"(load \"helpers.sema\")",
";; helpers.sema\n(define (greet name)\n (format \"Hello, ~a\" name))\n\n;; main.sema\n(load \"helpers.sema\")\n(greet \"World\") ; => \"Hello, World\"",
"(define result (load \"config.sema\"))\n;; if config.sema ends with (define port 8080), result is 8080"
],
"body": "Load and evaluate a Sema source file in the current environment. The file is read, parsed, and each top-level expression is evaluated sequentially. The last expression's value is returned. Unlike `import`, `load` does not use the module system — all top-level definitions from the loaded file become available directly in the current scope.\n\nPaths are resolved relative to the current file's directory. If the path is absolute, it is used as-is. `load` checks the Virtual File System (VFS) first, which is used for bundled executables and embedded packages. The `load` form requires the `FS_READ` sandbox capability.\n\n**Warning:** Using `load` pollutes the current namespace. Prefer `import` for structured module dependencies.\n\n```sema\n(load \"helpers.sema\")\n```\n\nLoading a file that defines utilities:\n\n```sema\n;; helpers.sema\n(define (greet name)\n (format \"Hello, ~a\" name))\n\n;; main.sema\n(load \"helpers.sema\")\n(greet \"World\") ; => \"Hello, World\"\n```\n\n`load` returns the value of the last expression evaluated in the file:\n\n```sema\n(define result (load \"config.sema\"))\n;; if config.sema ends with (define port 8080), result is 8080\n```",
"syntax": "(load \"filename\")",
"special_form": true
},
{
"name": "macroexpand",
"module": "special-forms",
"summary": "Evaluate the argument to obtain a form, then expand it one level if its head names a macro. Non-macro forms are returned unchanged. This is the primary tool for inspecting how macros rewrite code during development and debugging.",
"examples": [
"(macroexpand '(when-let (x 1) x))\n; => (let ((x 1)) (when (not (nil? x)) x))",
"(macroexpand '(-> 5 (+ 3) (* 2)))\n; => (* (+ 5 3) 2)",
"(macroexpand '(+ 1 2))\n; => (+ 1 2) ; not a macro, returned unchanged"
],
"body": "Evaluate the argument to obtain a form, then expand it one level if its head names a macro. Non-macro forms are returned unchanged. This is the primary tool for inspecting how macros rewrite code during development and debugging.\n\n`macroexpand` first evaluates its argument (so you typically pass a quoted form), then checks whether the result is a list whose first element names a macro in the current environment. If so, it applies the macro once and returns the expanded result without further evaluating it. If the head is not a macro, or if the form is not a list, the form is returned as-is. Only a single level of expansion is performed — nested macro calls inside the expansion are not expanded.\n\n```sema\n(macroexpand '(when-let (x 1) x))\n; => (let ((x 1)) (when (not (nil? x)) x))\n```\n\n```sema\n(macroexpand '(-> 5 (+ 3) (* 2)))\n; => (* (+ 5 3) 2)\n```\n\n```sema\n(macroexpand '(+ 1 2))\n; => (+ 1 2) ; not a macro, returned unchanged\n```\n\n**Warning:** `macroexpand` evaluates its argument before expanding. If you pass an unquoted expression that evaluates to a macro call, it will expand that result. To inspect a literal form, always quote it.",
"syntax": "(macroexpand form)",
"special_form": true
},
{
"name": "match",
"module": "special-forms",
"summary": "Pattern-match a value against a series of clauses. Each clause consists of a pattern, an optional `when` guard, and one or more body expressions. The first clause whose pattern matches the value and whose guard (if present) evaluates to truthy is executed, and its last body's value is returned. If no clause matches, `match` returns `nil`.",
"examples": [
"(match status\n (:ok \"success\")\n (:error \"failure\")\n (_ \"unknown\"))",
"(match '(1 2 3)\n ([a b c] (+ a b c)))\n;; => 6",
"(match response\n ({:type :ok :data d} (process d))\n ({:type :error :msg m} (log-error m))\n (_ (println \"unknown\")))",
"(match n\n (x when (> x 100) \"big\")\n (x when (> x 0) \"small\")\n (_ \"non-positive\"))"
],
"body": "Pattern-match a value against a series of clauses. Each clause consists of a pattern, an optional `when` guard, and one or more body expressions. The first clause whose pattern matches the value and whose guard (if present) evaluates to truthy is executed, and its last body's value is returned. If no clause matches, `match` returns `nil`.\n\nPatterns can be literals (numbers, strings, keywords, symbols), vectors (matching lists or vectors by structure), maps (matching maps by keys), or binding patterns (symbols that capture the matched value). The wildcard `_` matches any value without binding it. Nested patterns are fully supported, allowing deep structural matching in a single clause. Guards add arbitrary boolean conditions after a pattern match using `when`.\n\n```sema\n(match status\n (:ok \"success\")\n (:error \"failure\")\n (_ \"unknown\"))\n```\n\n```sema\n(match '(1 2 3)\n ([a b c] (+ a b c)))\n;; => 6\n```\n\n```sema\n(match response\n ({:type :ok :data d} (process d))\n ({:type :error :msg m} (log-error m))\n (_ (println \"unknown\")))\n```\n\n```sema\n(match n\n (x when (> x 100) \"big\")\n (x when (> x 0) \"small\")\n (_ \"non-positive\"))\n```\n\n**Note:** `match` lowers to nested `if`/`let*` chains with a runtime `__vm-try-match` helper.",
"syntax": "(match expr [pattern body ...] [pattern when guard body ...] ...)",
"special_form": true
},
{
"name": "message",
"module": "special-forms",
"summary": "Construct a single chat message value. The first argument must be a role keyword — one of `:system`, `:user`, `:assistant`, or `:tool`. The remaining arguments are evaluated and their results concatenated into the message content. String values are appended directly; non-string values are converted to strings automatically. The resulting message value can be used standalone or combined inside a `prompt` form.",
"examples": [
"(message :user \"Hello, \" \"world\")\n; => a message with role :user and content \"Hello, world\"",
"(message :user \"The answer is \" (+ 6 7))\n; => content \"The answer is 13\"",
"(define m (message :system \"You are a Sema tutor.\"))\n(prompt m (message :user \"How do I use destructuring?\"))"
],
"body": "Construct a single chat message value. The first argument must be a role keyword — one of `:system`, `:user`, `:assistant`, or `:tool`. The remaining arguments are evaluated and their results concatenated into the message content. String values are appended directly; non-string values are converted to strings automatically. The resulting message value can be used standalone or combined inside a `prompt` form.\n\n```sema\n(message :user \"Hello, \" \"world\")\n; => a message with role :user and content \"Hello, world\"\n```\n\nNon-string values are automatically stringified, which makes it convenient to embed computed results directly into messages.\n\n```sema\n(message :user \"The answer is \" (+ 6 7))\n; => content \"The answer is 13\"\n```\n\nMessages are typically composed into a prompt for LLM calls, but they can also be inspected or manipulated directly.\n\n```sema\n(define m (message :system \"You are a Sema tutor.\"))\n(prompt m (message :user \"How do I use destructuring?\"))\n```\n\n**Note:** `message` is a special form rather than a regular function because it evaluates its arguments individually and concatenates their string representations, producing a structured `Message` value rather than a plain string.",
"syntax": "(message role content ...)",
"special_form": true
},
{
"name": "module",
"module": "special-forms",
"summary": "Declare a module within a file. The first argument is the module name as a symbol. The second argument must be an export declaration of the form `(export sym1 sym2 ...)`, which lists the symbols that should be visible to code that imports this file. The remaining arguments are the module body expressions, evaluated in order.",
"examples": [
"(module math\n (export square factorial)\n (define (square x) (* x x))\n (define (factorial n)\n (if (<= n 1)\n 1\n (* n (factorial (- n 1)))))\n (define (helper x) (* x 2))) ; private, not exported",
"(module utils\n (export clamp lerp)\n (define (clamp x lo hi)\n (cond ((< x lo) lo) ((> x hi) hi) (else x)))\n (define (lerp a b t)\n (+ a (* t (- b a)))))",
"(import \"math.sema\")\n(square 5) ; => 25\n(factorial 5) ; => 120",
"(import \"math.sema\" square)"
],
"body": "Declare a module within a file. The first argument is the module name as a symbol. The second argument must be an export declaration of the form `(export sym1 sym2 ...)`, which lists the symbols that should be visible to code that imports this file. The remaining arguments are the module body expressions, evaluated in order.\n\nOnly names listed in the export clause are exposed to importers. Unexported names remain private to the module. The module system is used in conjunction with `import`, which loads a file containing one or more `module` declarations and selectively brings exported bindings into scope.\n\n```sema\n(module math\n (export square factorial)\n (define (square x) (* x x))\n (define (factorial n)\n (if (<= n 1)\n 1\n (* n (factorial (- n 1)))))\n (define (helper x) (* x 2))) ; private, not exported\n```\n\nA file can contain multiple module declarations, though typically one per file is used:\n\n```sema\n(module utils\n (export clamp lerp)\n (define (clamp x lo hi)\n (cond ((< x lo) lo) ((> x hi) hi) (else x)))\n (define (lerp a b t)\n (+ a (* t (- b a)))))\n```\n\nImporting a module with `import`:\n\n```sema\n(import \"math.sema\")\n(square 5) ; => 25\n(factorial 5) ; => 120\n```\n\nSelective import is supported by passing the desired symbols to `import`:\n\n```sema\n(import \"math.sema\" square)\n```",
"syntax": "(module name (export sym1 sym2 ...) body ...)",
"special_form": true
},
{
"name": "or",
"module": "special-forms",
"summary": "Short-circuit logical OR. Evaluates expressions from left to right and returns the first truthy value it encounters. If all expressions are falsy, it returns the value of the last expression. With no arguments, `or` returns `#f`.",
"examples": [
"(or #f #t) ; => #t\n(or #f #f) ; => #f",
"(define config nil)\n(or config {}) ; => {} (default when config is nil)",
"(define port (or (:port env) 8080))\nport ; => 8080 if env has no :port key",
"(or) ; => #f\n(or 42 (crash)) ; => 42 (crash is never called)"
],
"body": "Short-circuit logical OR. Evaluates expressions from left to right and returns the first truthy value it encounters. If all expressions are falsy, it returns the value of the last expression. With no arguments, `or` returns `#f`.\n\nBecause it is a special form, `or` stops evaluating as soon as a truthy result is found; subsequent expressions are never touched. This is useful for providing default values, trying alternatives, or fallbacks. Only `nil` and `#f` are falsy in Sema.\n\n```sema\n(or #f #t) ; => #t\n(or #f #f) ; => #f\n```\n\n```sema\n(define config nil)\n(or config {}) ; => {} (default when config is nil)\n```\n\n```sema\n(define port (or (:port env) 8080))\nport ; => 8080 if env has no :port key\n```\n\n```sema\n(or) ; => #f\n(or 42 (crash)) ; => 42 (crash is never called)\n```",
"syntax": "(or expr ...)",
"special_form": true
},
{
"name": "progn",
"module": "special-forms",
"summary": "Alias for `begin`. Evaluates each expression in order and returns the value of the last one. All intermediate results are discarded, making it useful for grouping side effects into a single expression. The last expression is evaluated in tail position, so tail-call optimization applies.",
"examples": [
"(progn\n (println \"side effect\")\n (+ 1 2)) ; => 3",
"(define counter 0)\n(progn\n (set! counter (+ counter 1))\n (set! counter (+ counter 1))\n counter) ; => 2",
"(progn) ; => nil",
"(let ((x 10))\n (progn\n (println x)\n (* x 2))) ; => 20"
],
"body": "Alias for `begin`. Evaluates each expression in order and returns the value of the last one. All intermediate results are discarded, making it useful for grouping side effects into a single expression. The last expression is evaluated in tail position, so tail-call optimization applies.\n\n`progn` exists for compatibility with Common Lisp and other Lisp dialects. It behaves identically to `begin` in every way, including returning `nil` when given no arguments. New code should generally prefer `begin`, but either form is fully supported.\n\n```sema\n(progn\n (println \"side effect\")\n (+ 1 2)) ; => 3\n```\n\n```sema\n(define counter 0)\n(progn\n (set! counter (+ counter 1))\n (set! counter (+ counter 1))\n counter) ; => 2\n```\n\n```sema\n(progn) ; => nil\n```\n\n```sema\n(let ((x 10))\n (progn\n (println x)\n (* x 2))) ; => 20\n```",
"syntax": "(progn body ...)",
"special_form": true
},
{
"name": "prompt",
"module": "special-forms",
"summary": "Build an LLM prompt value from a sequence of messages. Each argument to `prompt` is either a `(role content ...)` form or an existing message value produced by the `message` form. The supported role names are `system`, `user`, `assistant`, and `tool`, passed as bare symbols (not keywords). When a role form is used, the remaining arguments in that form are evaluated and their results concatenated into the message content. String values are appended directly; non-string values are stringified. The resulting prompt value can be passed to LLM functions such as `llm/chat`, `llm/stream`, or `llm/complete`.",
"examples": [
"(prompt\n (system \"You are a helpful assistant.\")\n (user \"Explain recursion in one sentence\"))",
"(define system-msg (message :system \"You are a Sema expert.\"))\n(define user-msg (message :user \"What is a macro?\"))\n(prompt system-msg user-msg)",
"(prompt\n (system \"You are a coding assistant.\")\n (user \"Write a factorial function in Sema.\")\n (assistant \"(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))\")\n (user \"Can you optimize it with tail recursion?\"))"
],
"body": "Build an LLM prompt value from a sequence of messages. Each argument to `prompt` is either a `(role content ...)` form or an existing message value produced by the `message` form. The supported role names are `system`, `user`, `assistant`, and `tool`, passed as bare symbols (not keywords). When a role form is used, the remaining arguments in that form are evaluated and their results concatenated into the message content. String values are appended directly; non-string values are stringified. The resulting prompt value can be passed to LLM functions such as `llm/chat`, `llm/stream`, or `llm/complete`.\n\n```sema\n(prompt\n (system \"You are a helpful assistant.\")\n (user \"Explain recursion in one sentence\"))\n```\n\nYou can mix role forms with pre-built message values, which is useful when constructing prompts dynamically or reusing message fragments.\n\n```sema\n(define system-msg (message :system \"You are a Sema expert.\"))\n(define user-msg (message :user \"What is a macro?\"))\n(prompt system-msg user-msg)\n```\n\nFor multi-turn conversations, simply list messages in order. The LLM will see them as a single conversation thread.\n\n```sema\n(prompt\n (system \"You are a coding assistant.\")\n (user \"Write a factorial function in Sema.\")\n (assistant \"(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))\")\n (user \"Can you optimize it with tail recursion?\"))\n```\n\n**Note:** `prompt` is a special form because it does not evaluate its arguments in the normal function-call way — it inspects each argument to detect role forms before evaluating their content parts.",
"syntax": "(prompt (role content ...) ... )",
"special_form": true
},
{
"name": "quasiquote",
"module": "special-forms",
"summary": "`quasiquote` creates a data template with selective evaluation. Like `quote`, it prevents evaluation of the overall form, but inside a quasiquote you can use `,expr` (unquote) to evaluate individual expressions and insert their results. You can also use `,@expr` (unquote-splicing) to evaluate an expression that must produce a list or vector, and splice each of its elements into the surrounding sequence.",
"examples": [
"(define x 42)\n`(a b ,x)\n;; => (a b 42)",
"`(a ,@(list 1 2 3) b)\n;; => (a 1 2 3 b)",
"(defmacro my-let1 (val body)\n `(let ((x# ,val)) ,body))\n(let ((x 10))\n (my-let1 42 x))\n;; => 10"
],
"body": "`quasiquote` creates a data template with selective evaluation. Like `quote`, it prevents evaluation of the overall form, but inside a quasiquote you can use `,expr` (unquote) to evaluate individual expressions and insert their results. You can also use `,@expr` (unquote-splicing) to evaluate an expression that must produce a list or vector, and splice each of its elements into the surrounding sequence.\n\nQuasiquote works with lists, vectors, and maps. It also supports auto-gensym: any symbol ending with `#` (but not `##`) is replaced by a unique generated symbol that is consistent across all occurrences within the same quasiquote form. This is invaluable for writing hygienic macros.\n\nThe reader shorthand `` ` `` expands to `quasiquote`, so `` `(a ,b) `` is equivalent to `(quasiquote (a (unquote b)))`.\n\n```sema\n(define x 42)\n`(a b ,x)\n;; => (a b 42)\n```\n\n```sema\n`(a ,@(list 1 2 3) b)\n;; => (a 1 2 3 b)\n```\n\n```sema\n(defmacro my-let1 (val body)\n `(let ((x# ,val)) ,body))\n(let ((x 10))\n (my-let1 42 x))\n;; => 10\n```\n\n**Note:** Unquote-splicing can only be used inside sequences (lists or vectors). Splicing into a map is not supported.",
"syntax": "(quasiquote expr)",
"special_form": true
},
{
"name": "quote",
"module": "special-forms",
"summary": "`quote` returns its argument unevaluated. It prevents the evaluator from treating a list as a function call or a symbol as a variable reference. The reader shorthand `'` (apostrophe) desugars directly to `quote`, so `'x` is equivalent to `(quote x)`.",
"examples": [
"(quote (1 2 3))\n;; => (1 2 3)",
"'(+ 1 2)\n;; => (+ 1 2)",
"'foo\n;; => foo",
"'(a (b c) d)\n;; => (a (b c) d)"
],
"body": "`quote` returns its argument unevaluated. It prevents the evaluator from treating a list as a function call or a symbol as a variable reference. The reader shorthand `'` (apostrophe) desugars directly to `quote`, so `'x` is equivalent to `(quote x)`.\n\nThis form is fundamental for constructing data literals and for writing macros, because it lets you treat code as data.\n\n```sema\n(quote (1 2 3))\n;; => (1 2 3)\n```\n\n```sema\n'(+ 1 2)\n;; => (+ 1 2)\n```\n\n```sema\n'foo\n;; => foo\n```\n\n```sema\n'(a (b c) d)\n;; => (a (b c) d)\n```\n\n**Note:** Quoting a symbol produces the symbol value itself, not the value bound to that name in the environment.",
"syntax": "(quote expr)",
"special_form": true
},
{
"name": "set!",
"module": "special-forms",
"summary": "Mutate an existing variable binding. `set!` evaluates its second argument and assigns the result to the binding named by the first argument, which must be a symbol. The binding must already exist in the current environment chain; `set!` cannot create new bindings.",
"examples": [
"(define x 1)\n(set! x 2)\nx ; => 2",
"(let ((total 0))\n (set! total (+ total 10))\n total) ; => 10",
"(let ((n 0))\n (while (< n 3)\n (println n)\n (set! n (+ n 1)))\n n) ; => 3"
],
"body": "Mutate an existing variable binding. `set!` evaluates its second argument and assigns the result to the binding named by the first argument, which must be a symbol. The binding must already exist in the current environment chain; `set!` cannot create new bindings.\n\nIf the symbol is not bound, `set!` raises an `:unbound` error. In interactive contexts, the error message may include a suggestion for a similar existing name when one is found in the environment.\n\nUnlike functional rebinding with `let`, `set!` performs imperative mutation and is commonly used with `while` loops and other stateful algorithms. It always returns `nil`.\n\n```sema\n(define x 1)\n(set! x 2)\nx ; => 2\n```\n\n```sema\n(let ((total 0))\n (set! total (+ total 10))\n total) ; => 10\n```\n\n```sema\n(let ((n 0))\n (while (< n 3)\n (println n)\n (set! n (+ n 1)))\n n) ; => 3\n```",
"syntax": "(set! name value)",
"special_form": true
},
{
"name": "some->",
"module": "special-forms",
"summary": "Nil-safe thread-first macro. Threads `val` through a sequence of forms, inserting it as the first argument of each form, but short-circuits and returns `nil` as soon as any intermediate step produces `nil`. This is useful for safely navigating nested data structures or chaining operations where any step might legitimately return `nil`.",
"examples": [
"(some-> {:a {:b 1}} (get :a) (get :b)) ; => 1\n(some-> {:a {:b 1}} (get :x) (get :b)) ; => nil",
"(some-> config :database :connection-string db/connect)\n;; returns nil if any step is nil, instead of crashing",
"(some-> 5 (+ 3) (* 2)) ; => 16\n(some-> nil (+ 3) (* 2)) ; => nil"
],
"body": "Nil-safe thread-first macro. Threads `val` through a sequence of forms, inserting it as the first argument of each form, but short-circuits and returns `nil` as soon as any intermediate step produces `nil`. This is useful for safely navigating nested data structures or chaining operations where any step might legitimately return `nil`.\n\n`some->` is a built-in macro defined in the prelude. It expands to a nested `let` and `if` expression using an auto-gensym temporary variable to avoid variable capture. If a form is a list, `val` is inserted after the function position; if a form is a bare symbol, it is treated as a function call with `val` as its sole argument.\n\n```sema\n(some-> {:a {:b 1}} (get :a) (get :b)) ; => 1\n(some-> {:a {:b 1}} (get :x) (get :b)) ; => nil\n```\n\n```sema\n(some-> config :database :connection-string db/connect)\n;; returns nil if any step is nil, instead of crashing\n```\n\n```sema\n(some-> 5 (+ 3) (* 2)) ; => 16\n(some-> nil (+ 3) (* 2)) ; => nil\n```\n\n**Note:** Unlike `->`, which would attempt to call the next form with `nil` and likely raise a type or arity error, `some->` safely aborts the pipeline. Use `->` when you expect every step to succeed, and `some->` when `nil` is a valid intermediate result.",
"syntax": "(some-> val form ...)",
"special_form": true
},
{
"name": "throw",
"module": "special-forms",
"summary": "Raise a user exception with the given value. The value is evaluated and then wrapped in a `:user` error, which can be caught by `try` and its `catch` clause. When caught, the error map contains `:type` set to `:user`, `:message` set to the string representation of the value, and `:value` set to the original value. This makes `throw` the companion form to `try` for explicit error signaling.",
"examples": [
"(throw \"something went wrong\")",
"(throw {:code 404 :reason \"not found\"})",
"(try\n (throw {:status :error :detail \"disk full\"})\n (catch e\n (println (:message e))\n (:value e)))\n; => {:status :error :detail \"disk full\"}"
],
"body": "Raise a user exception with the given value. The value is evaluated and then wrapped in a `:user` error, which can be caught by `try` and its `catch` clause. When caught, the error map contains `:type` set to `:user`, `:message` set to the string representation of the value, and `:value` set to the original value. This makes `throw` the companion form to `try` for explicit error signaling.\n\n```sema\n(throw \"something went wrong\")\n```\n\nAny value can be thrown, not just strings. Maps and lists are useful for structured error information.\n\n```sema\n(throw {:code 404 :reason \"not found\"})\n```\n\nA thrown value is caught by `try` and appears in the catch variable under the `:value` key.\n\n```sema\n(try\n (throw {:status :error :detail \"disk full\"})\n (catch e\n (println (:message e))\n (:value e)))\n; => {:status :error :detail \"disk full\"}\n```\n\nIf a `throw` is not caught, the error propagates up the call stack until it is either caught by an outer `try` or causes the program to abort with the error message.\n\n**Note:** Unlike many Lisp dialects where `throw` pairs with a catch tag, Sema's `throw` always works with `try`/`catch` in the style of modern language exception handling.",
"syntax": "(throw value)",
"special_form": true
},
{
"name": "try",
"module": "special-forms",
"summary": "Evaluate a sequence of body expressions and, if any error occurs, catch it and evaluate the handler expressions. The last argument must be a catch clause of the form `(catch var handler ...)`, where `var` is a symbol that binds the caught error. All body expressions except the catch clause are evaluated in order; if none raise an error, the result of the last body expression is returned. If an error occurs, evaluation stops, the error is bound to `var` in a fresh child environment, and the handler expressions are evaluated.",
"examples": [
"(try\n (/ 1 0)\n (catch e\n (println \"Error:\" (:message e))\n (:type e)))\n; => :eval",
"(try\n (some-operation)\n (catch e\n (cond\n ((= (:type e) :permission-denied)\n (println \"Access denied!\"))\n ((= (:type e) :user)\n (println \"User error:\" (:message e)))\n (else\n (throw e)))))"
],
"body": "Evaluate a sequence of body expressions and, if any error occurs, catch it and evaluate the handler expressions. The last argument must be a catch clause of the form `(catch var handler ...)`, where `var` is a symbol that binds the caught error. All body expressions except the catch clause are evaluated in order; if none raise an error, the result of the last body expression is returned. If an error occurs, evaluation stops, the error is bound to `var` in a fresh child environment, and the handler expressions are evaluated.\n\nThe caught error is a map value with at least the following keys:\n- `:type` — a keyword indicating the error category (e.g. `:user`, `:eval`, `:type-error`, `:arity`, `:unbound`, `:permission-denied`, `:reader`, `:llm`, `:io`)\n- `:message` — a human-readable description string\n- `:stack-trace` — a list of stack frames with `:name`, `:file`, `:line`, and `:col`\n\nAdditional keys may be present depending on the error type. For example, `:user` errors (thrown with `throw`) include `:value` containing the original thrown value. `:type-error` includes `:expected` and `:got`. `:unbound` includes `:name`.\n\n```sema\n(try\n (/ 1 0)\n (catch e\n (println \"Error:\" (:message e))\n (:type e)))\n; => :eval\n```\n\nUse the `:type` field to discriminate specific errors and re-throw anything you do not intend to handle. Catching all errors can silently mask bugs such as typos in variable names or arity mismatches.\n\n```sema\n(try\n (some-operation)\n (catch e\n (cond\n ((= (:type e) :permission-denied)\n (println \"Access denied!\"))\n ((= (:type e) :user)\n (println \"User error:\" (:message e)))\n (else\n (throw e)))))\n```\n\nThe handler body supports tail-call optimization on its last expression, just like a regular `begin` block.\n\n**Warning:** `try` catches **all** error types, not just user exceptions thrown with `throw`. This includes internal errors like `:unbound` (undefined variables), `:arity` (wrong number of arguments), and `:permission-denied` (sandbox violations). Always re-throw errors you do not intend to handle.",
"syntax": "(try body ... (catch var handler ...))",
"special_form": true
},
{
"name": "unless",
"module": "special-forms",
"summary": "Inverse of `when`: evaluates its body expressions only when `condition` is falsy. If the condition is truthy, `unless` returns `nil` without evaluating any body expression. It accepts any number of body expressions and is useful for guard clauses or early-exit style code.",
"examples": [
"(unless (> x 0) (println \"non-positive\"))",
"(unless (authenticated? user)\n (println \"Access denied\")\n (return 403))",
"(unless nil\n (println \"nil is falsy, so this prints\")) ; prints and returns nil",
"(unless #t\n (println \"skipped\")) ; => nil (#t is truthy, body skipped)"
],
"body": "Inverse of `when`: evaluates its body expressions only when `condition` is falsy. If the condition is truthy, `unless` returns `nil` without evaluating any body expression. It accepts any number of body expressions and is useful for guard clauses or early-exit style code.\n\nLike all conditionals in Sema, only `nil` and `#f` are falsy; every other value is truthy. Use `unless` when you want to express \"do this only if NOT\" without nesting an `if` or swapping a predicate.\n\n```sema\n(unless (> x 0) (println \"non-positive\"))\n```\n\n```sema\n(unless (authenticated? user)\n (println \"Access denied\")\n (return 403))\n```\n\n```sema\n(unless nil\n (println \"nil is falsy, so this prints\")) ; prints and returns nil\n```\n\n```sema\n(unless #t\n (println \"skipped\")) ; => nil (#t is truthy, body skipped)\n```",
"syntax": "(unless condition body ...)",
"special_form": true
},
{
"name": "when",
"module": "special-forms",
"summary": "Single-branch conditional that evaluates its body expressions only when `condition` is truthy. If the condition is falsy, `when` returns `nil` without evaluating any body expression. Unlike `if`, `when` accepts any number of body expressions and always returns `nil` when the condition is falsy.",
"examples": [
"(when (> x 0) (println \"positive\"))",
"(when (file-exists? path)\n (println \"Loading config...\")\n (load path)\n (println \"Done.\"))",
"(when #f\n (println \"this never prints\")) ; => nil",
"(define n 7)\n(when (even? n)\n (println \"even\")\n (* n 2)) ; => nil (n is odd, body skipped)"
],
"body": "Single-branch conditional that evaluates its body expressions only when `condition` is truthy. If the condition is falsy, `when` returns `nil` without evaluating any body expression. Unlike `if`, `when` accepts any number of body expressions and always returns `nil` when the condition is falsy.\n\nUse `when` when you need a side-effecting block that should run only under a certain condition and do not need an else branch. Because the body is not evaluated unless the condition holds, it is safe to use for guarded computations.\n\n```sema\n(when (> x 0) (println \"positive\"))\n```\n\n```sema\n(when (file-exists? path)\n (println \"Loading config...\")\n (load path)\n (println \"Done.\"))\n```\n\n```sema\n(when #f\n (println \"this never prints\")) ; => nil\n```\n\n```sema\n(define n 7)\n(when (even? n)\n (println \"even\")\n (* n 2)) ; => nil (n is odd, body skipped)\n```",
"syntax": "(when condition body ...)",
"special_form": true
},
{
"name": "when-let",
"module": "special-forms",
"summary": "`when-let` is a built-in macro that evaluates `expr`, binds the result to `var`, and evaluates the body only if the value is non-nil. If the value is `nil`, the entire form returns `nil` without evaluating the body. The binding is only visible inside the body expressions.",
"examples": [
"(when-let (x (get {:a 1} :a)) (* x 10))\n;; => 10",
"(when-let (x (get {:a 1} :b)) (* x 10))\n;; => nil",
"(when-let (user (db/find-user id))\n (send-email user \"Welcome back\")\n (log-event \"login\" user))\n;; => performs side effects only when a user is found"
],
"body": "`when-let` is a built-in macro that evaluates `expr`, binds the result to `var`, and evaluates the body only if the value is non-nil. If the value is `nil`, the entire form returns `nil` without evaluating the body. The binding is only visible inside the body expressions.\n\nThis form is ideal for conditional side effects or computations that depend on a value that might be absent.\n\n```sema\n(when-let (x (get {:a 1} :a)) (* x 10))\n;; => 10\n```\n\n```sema\n(when-let (x (get {:a 1} :b)) (* x 10))\n;; => nil\n```\n\n```sema\n(when-let (user (db/find-user id))\n (send-email user \"Welcome back\")\n (log-event \"login\" user))\n;; => performs side effects only when a user is found\n```\n\n**Note:** `when-let` expands to a `let` and a `when`. It is defined in the prelude and does not require an import.",
"syntax": "(when-let (var expr) body ...)",
"special_form": true
},
{
"name": "while",
"module": "special-forms",
"summary": "Repeatedly evaluate the body expressions while the condition is truthy. The condition is tested before each iteration. If the condition is initially falsy, the body is never executed. `while` always returns `nil`. Loop variables must be mutated explicitly with `set!` or updated through mutable data structures.",
"examples": [
"(define i 0)\n(while (< i 5)\n (println i)\n (set! i (+ i 1)))\n;; prints 0 through 4",
"(let ((n 100))\n (while (> n 1)\n (println n)\n (set! n (if (even? n) (/ n 2) (+ (* n 3) 1)))))\n;; Collatz sequence",
"(let ((found #f)\n (i 0))\n (while (and (not found) (< i 100))\n (when (= (nth items i) target)\n (set! found #t))\n (set! i (+ i 1)))\n found)"
],
"body": "Repeatedly evaluate the body expressions while the condition is truthy. The condition is tested before each iteration. If the condition is initially falsy, the body is never executed. `while` always returns `nil`. Loop variables must be mutated explicitly with `set!` or updated through mutable data structures.\n\n`while` is the simplest imperative looping construct in Sema. It is well-suited for loops where the termination condition depends on external state or complex logic that is awkward to express with `do` or named `let`. Because the condition and body are re-evaluated on each iteration, be careful to ensure the loop makes progress to avoid infinite loops.\n\n```sema\n(define i 0)\n(while (< i 5)\n (println i)\n (set! i (+ i 1)))\n;; prints 0 through 4\n```\n\n```sema\n(let ((n 100))\n (while (> n 1)\n (println n)\n (set! n (if (even? n) (/ n 2) (+ (* n 3) 1)))))\n;; Collatz sequence\n```\n\n```sema\n(let ((found #f)\n (i 0))\n (while (and (not found) (< i 100))\n (when (= (nth items i) target)\n (set! found #t))\n (set! i (+ i 1)))\n found)\n```\n\n**Note:** The VM compiles `while` to a conditional jump loop.",
"syntax": "(while condition body ...)",
"special_form": true
},
{
"name": "db/close",
"module": "sqlite",
"section": "Opening & Closing",
"summary": "Close a database connection and release the handle. Returns `nil`.",
"examples": [
"(db/close \"mydb\")"
],
"body": "Close a database connection and release the handle. Returns `nil`.\n\n```sema\n(db/close \"mydb\")\n```"
},
{
"name": "db/exec",
"module": "sqlite",
"section": "Executing SQL",
"summary": "Execute a SQL statement that modifies data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Returns the number of affected rows as an integer. Supports parameterized queries.",
"examples": [
"(db/exec \"mydb\" \"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)\")\n; => 0\n\n(db/exec \"mydb\" \"INSERT INTO users (name, age) VALUES (?, ?)\" \"Alice\" 30)\n; => 1\n\n(db/exec \"mydb\" \"UPDATE users SET age = ? WHERE name = ?\" 31 \"Alice\")\n; => 1"
],
"body": "Execute a SQL statement that modifies data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Returns the number of affected rows as an integer. Supports parameterized queries.\n\n```sema\n(db/exec \"mydb\" \"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)\")\n; => 0\n\n(db/exec \"mydb\" \"INSERT INTO users (name, age) VALUES (?, ?)\" \"Alice\" 30)\n; => 1\n\n(db/exec \"mydb\" \"UPDATE users SET age = ? WHERE name = ?\" 31 \"Alice\")\n; => 1\n```"
},
{
"name": "db/exec-batch",
"module": "sqlite",
"section": "Executing SQL",
"summary": "Execute multiple SQL statements at once. STATIC SQL ONLY — there is no parameter binding, so the entire string is run verbatim. Useful for schema setup and migrations. Returns `nil`.",
"examples": [
"(db/exec-batch \"mydb\" \"\n CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT);\n CREATE TABLE tags (id INTEGER PRIMARY KEY, name TEXT);\n CREATE INDEX idx_posts_user ON posts(user_id);\n\")"
],
"body": "Execute multiple SQL statements at once. STATIC SQL ONLY — there is no parameter binding, so the entire string is run verbatim. Useful for schema setup and migrations. Returns `nil`.\n\n**Security:** never interpolate user-controlled input into the SQL string passed to `db/exec-batch` — doing so is a SQL injection vulnerability. For any value that comes from outside the program, use the parameterized `db/exec` (with `?` placeholders) instead, one statement at a time.\n\n```sema\n(db/exec-batch \"mydb\" \"\n CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT);\n CREATE TABLE tags (id INTEGER PRIMARY KEY, name TEXT);\n CREATE INDEX idx_posts_user ON posts(user_id);\n\")\n```"
},
{
"name": "db/last-insert-id",
"module": "sqlite",
"section": "Utility",
"summary": "Return the rowid of the last inserted row.",
"examples": [
"(db/exec \"mydb\" \"INSERT INTO users (name, age) VALUES (?, ?)\" \"Bob\" 25)\n(db/last-insert-id \"mydb\")\n; => 2"
],
"body": "Return the rowid of the last inserted row.\n\n```sema\n(db/exec \"mydb\" \"INSERT INTO users (name, age) VALUES (?, ?)\" \"Bob\" 25)\n(db/last-insert-id \"mydb\")\n; => 2\n```"
},
{
"name": "db/open",
"module": "sqlite",
"section": "Opening & Closing",
"summary": "Open (or create) a SQLite database file. Returns a handle string for use in subsequent calls. Enables WAL journal mode and foreign keys automatically.",
"examples": [
";; Open with path as handle\n(db/open \"mydata.db\") ; => \"mydata.db\"\n\n;; Open with a named handle\n(db/open \"mydb\" \"/path/to/data.db\") ; => \"mydb\""
],
"body": "Open (or create) a SQLite database file. Returns a handle string for use in subsequent calls. Enables WAL journal mode and foreign keys automatically.\n\n```sema\n;; Open with path as handle\n(db/open \"mydata.db\") ; => \"mydata.db\"\n\n;; Open with a named handle\n(db/open \"mydb\" \"/path/to/data.db\") ; => \"mydb\"\n```"
},
{
"name": "db/open-memory",
"module": "sqlite",
"section": "Opening & Closing",
"summary": "Open an in-memory SQLite database. Useful for tests, temporary data, and caching.",
"examples": [
"(db/open-memory) ; handle is \":memory:\"\n(db/open-memory \"testdb\") ; handle is \"testdb\""
],
"body": "Open an in-memory SQLite database. Useful for tests, temporary data, and caching.\n\n```sema\n(db/open-memory) ; handle is \":memory:\"\n(db/open-memory \"testdb\") ; handle is \"testdb\"\n```"
},
{
"name": "db/query",
"module": "sqlite",
"section": "Querying",
"summary": "Execute a SELECT query and return all results as a list of maps. Column names become keyword keys. Supports parameterized queries.",
"examples": [
"(db/query \"mydb\" \"SELECT * FROM users\")\n; => ({:id 1 :name \"Alice\" :age 31})\n\n(db/query \"mydb\" \"SELECT name, age FROM users WHERE age > ?\" 25)\n; => ({:age 31 :name \"Alice\"})"
],
"body": "Execute a SELECT query and return all results as a list of maps. Column names become keyword keys. Supports parameterized queries.\n\n```sema\n(db/query \"mydb\" \"SELECT * FROM users\")\n; => ({:id 1 :name \"Alice\" :age 31})\n\n(db/query \"mydb\" \"SELECT name, age FROM users WHERE age > ?\" 25)\n; => ({:age 31 :name \"Alice\"})\n```"
},
{
"name": "db/query-one",
"module": "sqlite",
"section": "Querying",
"summary": "Execute a SELECT query and return only the first row as a map, or `nil` if no rows match.",
"examples": [
"(db/query-one \"mydb\" \"SELECT * FROM users WHERE name = ?\" \"Alice\")\n; => {:id 1 :name \"Alice\" :age 31}\n\n(db/query-one \"mydb\" \"SELECT * FROM users WHERE name = ?\" \"Nobody\")\n; => nil"
],
"body": "Execute a SELECT query and return only the first row as a map, or `nil` if no rows match.\n\n```sema\n(db/query-one \"mydb\" \"SELECT * FROM users WHERE name = ?\" \"Alice\")\n; => {:id 1 :name \"Alice\" :age 31}\n\n(db/query-one \"mydb\" \"SELECT * FROM users WHERE name = ?\" \"Nobody\")\n; => nil\n```"
},
{
"name": "db/tables",
"module": "sqlite",
"section": "Utility",
"summary": "List all user-created tables in the database (excludes internal SQLite tables). Returns a list of strings.",
"examples": [
"(db/tables \"mydb\")\n; => (\"posts\" \"tags\" \"users\")"
],
"body": "List all user-created tables in the database (excludes internal SQLite tables). Returns a list of strings.\n\n```sema\n(db/tables \"mydb\")\n; => (\"posts\" \"tags\" \"users\")\n```"
},
{
"name": "*stderr*",
"module": "streams",
"section": "Standard Streams",
"summary": "The standard error stream. A writable, non-readable stream of type `\"stderr\"`. Use with `stream/write`, `stream/write-string`, etc.",
"returns": "stream",
"examples": [
"(stream/type *stderr*) ; => \"stderr\"\n(stream/writable? *stderr*) ; => #t"
],
"body": "The standard error stream. A writable, non-readable stream of type `\"stderr\"`. Use with `stream/write`, `stream/write-string`, etc.\n\n```sema\n(stream/type *stderr*) ; => \"stderr\"\n(stream/writable? *stderr*) ; => #t\n```"
},
{
"name": "*stdin*",
"module": "streams",
"section": "Standard Streams",
"summary": "The standard input stream. A readable, non-writable stream of type `\"stdin\"`. Use with `stream/read`, `stream/read-line`, etc.",
"returns": "stream",
"examples": [
"(stream/type *stdin*) ; => \"stdin\"\n(stream/readable? *stdin*) ; => #t"
],
"body": "The standard input stream. A readable, non-writable stream of type `\"stdin\"`. Use with `stream/read`, `stream/read-line`, etc.\n\n```sema\n(stream/type *stdin*) ; => \"stdin\"\n(stream/readable? *stdin*) ; => #t\n```"
},
{
"name": "*stdout*",
"module": "streams",
"section": "Standard Streams",
"summary": "The standard output stream. A writable, non-readable stream of type `\"stdout\"`. Use with `stream/write`, `stream/write-string`, etc.",
"returns": "stream",
"examples": [
"(stream/type *stdout*) ; => \"stdout\"\n(stream/writable? *stdout*) ; => #t"
],
"body": "The standard output stream. A writable, non-readable stream of type `\"stdout\"`. Use with `stream/write`, `stream/write-string`, etc.\n\n```sema\n(stream/type *stdout*) ; => \"stdout\"\n(stream/writable? *stdout*) ; => #t\n```"
},
{
"name": "stream/available?",
"module": "streams",
"section": "Introspection",
"summary": "Returns `#t` if data is ready to read without blocking.",
"examples": [
"(stream/available? (stream/from-string \"x\")) ;; => #t\n(stream/available? (stream/from-string \"\")) ;; => #f"
],
"body": "Returns `#t` if data is ready to read without blocking.\n\n```sema\n(stream/available? (stream/from-string \"x\")) ;; => #t\n(stream/available? (stream/from-string \"\")) ;; => #f\n```"
},
{
"name": "stream/byte-buffer",
"module": "streams",
"section": "Creating Streams",
"summary": "Create a read/write in-memory buffer. Writes append to the buffer; reads consume from the current position.",
"examples": [
"(define buf (stream/byte-buffer))\n(stream/write buf (string->utf8 \"hello\"))\n(stream/to-string buf) ;; => \"hello\""
],
"body": "Create a read/write in-memory buffer. Writes append to the buffer; reads consume from the current position.\n\n```sema\n(define buf (stream/byte-buffer))\n(stream/write buf (string->utf8 \"hello\"))\n(stream/to-string buf) ;; => \"hello\"\n```"
},
{
"name": "stream/close",
"module": "streams",
"section": "Control",
"summary": "Close a stream, releasing the underlying resource. Double-close is a no-op.",
"examples": [
"(stream/close s)\n(stream/close s) ; safe, does nothing"
],
"body": "Close a stream, releasing the underlying resource. Double-close is a no-op.\n\n```sema\n(stream/close s)\n(stream/close s) ; safe, does nothing\n```"
},
{
"name": "stream/copy",
"module": "streams",
"section": "Control",
"summary": "Copy all bytes from one stream to another. Returns total bytes copied.",
"examples": [
"(with-stream (in (stream/open-input \"src.bin\"))\n (with-stream (out (stream/open-output \"dst.bin\"))\n (stream/copy in out))) ;; => bytes copied"
],
"body": "Copy all bytes from one stream to another. Returns total bytes copied.\n\n```sema\n(with-stream (in (stream/open-input \"src.bin\"))\n (with-stream (out (stream/open-output \"dst.bin\"))\n (stream/copy in out))) ;; => bytes copied\n```"
},
{
"name": "stream/flush",
"module": "streams",
"section": "Control",
"summary": "Flush any buffered output to the underlying sink.",
"examples": [
"(stream/flush s)"
],
"body": "Flush any buffered output to the underlying sink.\n\n```sema\n(stream/flush s)\n```"
},
{
"name": "stream/from-bytes",
"module": "streams",
"section": "Creating Streams",
"summary": "Create a readable stream from a bytevector.",
"examples": [
"(define s (stream/from-bytes (bytevector 1 2 3)))\n(stream/read-byte s) ;; => 1\n(stream/read-byte s) ;; => 2"
],
"body": "Create a readable stream from a bytevector.\n\n```sema\n(define s (stream/from-bytes (bytevector 1 2 3)))\n(stream/read-byte s) ;; => 1\n(stream/read-byte s) ;; => 2\n```"
},
{
"name": "stream/from-string",
"module": "streams",
"section": "Creating Streams",
"summary": "Create a read-only stream from a string's UTF-8 bytes.",
"examples": [
"(define s (stream/from-string \"hello world\"))\n(stream/read-byte s) ;; => 104 (ASCII 'h')\n(stream/read s 5) ;; => #u8(101 108 108 111 32) (\"ello \")"
],
"body": "Create a read-only stream from a string's UTF-8 bytes.\n\n```sema\n(define s (stream/from-string \"hello world\"))\n(stream/read-byte s) ;; => 104 (ASCII 'h')\n(stream/read s 5) ;; => #u8(101 108 108 111 32) (\"ello \")\n```"
},
{
"name": "stream/open-input",
"module": "streams",
"section": "Creating Streams",
"summary": "Open a file for reading. Returns a buffered input stream. Sandbox-gated (`FS_READ`).",
"examples": [
"(define s (stream/open-input \"data.csv\"))\n(define contents (stream/read-all s))\n(stream/close s)"
],
"body": "Open a file for reading. Returns a buffered input stream. Sandbox-gated (`FS_READ`).\n\n```sema\n(define s (stream/open-input \"data.csv\"))\n(define contents (stream/read-all s))\n(stream/close s)\n```"
},
{
"name": "stream/open-output",
"module": "streams",
"section": "Creating Streams",
"summary": "Open (or create) a file for writing. Returns a buffered output stream. Sandbox-gated (`FS_WRITE`).",
"examples": [
"(define s (stream/open-output \"output.txt\"))\n(stream/write-string s \"hello world\\n\")\n(stream/close s)"
],
"body": "Open (or create) a file for writing. Returns a buffered output stream. Sandbox-gated (`FS_WRITE`).\n\n```sema\n(define s (stream/open-output \"output.txt\"))\n(stream/write-string s \"hello world\\n\")\n(stream/close s)\n```"
},
{
"name": "stream/read",
"module": "streams",
"section": "Reading",
"summary": "Read up to `n` bytes, returning a bytevector. Returns fewer bytes at EOF.",
"examples": [
"(stream/read s 1024) ;; => bytevector (up to 1024 bytes)"
],
"body": "Read up to `n` bytes, returning a bytevector. Returns fewer bytes at EOF.\n\n```sema\n(stream/read s 1024) ;; => bytevector (up to 1024 bytes)\n```"
},
{
"name": "stream/read-all",
"module": "streams",
"section": "Reading",
"summary": "Read the entire stream into a bytevector.",
"examples": [
"(define data (stream/read-all s))\n(utf8->string data) ; convert to string if text"
],
"body": "Read the entire stream into a bytevector.\n\n```sema\n(define data (stream/read-all s))\n(utf8->string data) ; convert to string if text\n```"
},
{
"name": "stream/read-byte",
"module": "streams",
"section": "Reading",
"summary": "Read a single byte. Returns an integer 0–255, or `nil` at EOF.",
"examples": [
"(stream/read-byte s) ;; => 65 (or nil at EOF)"
],
"body": "Read a single byte. Returns an integer 0–255, or `nil` at EOF.\n\n```sema\n(stream/read-byte s) ;; => 65 (or nil at EOF)\n```"
},
{
"name": "stream/read-line",
"module": "streams",
"section": "Reading",
"summary": "Read until newline (`\\n`), returning a string without the newline. Strips trailing `\\r` for Windows line endings. Returns `nil` at EOF.",
"examples": [
"(stream/read-line s) ;; => \"first line\" (or nil)"
],
"body": "Read until newline (`\\n`), returning a string without the newline. Strips trailing `\\r` for Windows line endings. Returns `nil` at EOF.\n\n```sema\n(stream/read-line s) ;; => \"first line\" (or nil)\n```"
},
{
"name": "stream/readable?",
"aliases": [
"stream/writable?"
],
"module": "streams",
"section": "Introspection",
"summary": "Check the direction of a stream.",
"examples": [
"(stream/readable? (stream/from-string \"x\")) ;; => #t\n(stream/writable? (stream/from-string \"x\")) ;; => #f\n(stream/writable? (stream/byte-buffer)) ;; => #t"
],
"body": "Check the direction of a stream.\n\n```sema\n(stream/readable? (stream/from-string \"x\")) ;; => #t\n(stream/writable? (stream/from-string \"x\")) ;; => #f\n(stream/writable? (stream/byte-buffer)) ;; => #t\n```"
},
{
"name": "stream/to-bytes",
"module": "streams",
"section": "Extraction (Byte Buffers)",
"summary": "Extract the accumulated contents of a byte-buffer stream as a bytevector.",
"examples": [
"(let ((s (stream/byte-buffer)))\n (stream/write s (bytevector 1 2 3))\n (stream/to-bytes s)) ;; => #u8(1 2 3)"
],
"body": "Extract the accumulated contents of a byte-buffer stream as a bytevector.\n\n```sema\n(let ((s (stream/byte-buffer)))\n (stream/write s (bytevector 1 2 3))\n (stream/to-bytes s)) ;; => #u8(1 2 3)\n```"
},
{
"name": "stream/to-string",
"module": "streams",
"section": "Extraction (Byte Buffers)",
"summary": "Extract the contents of a byte-buffer stream as a UTF-8 string.",
"examples": [
"(let ((s (stream/byte-buffer)))\n (stream/write-string s \"hello\")\n (stream/to-string s)) ;; => \"hello\""
],
"body": "Extract the contents of a byte-buffer stream as a UTF-8 string.\n\n```sema\n(let ((s (stream/byte-buffer)))\n (stream/write-string s \"hello\")\n (stream/to-string s)) ;; => \"hello\"\n```"
},
{
"name": "stream/type",
"module": "streams",
"section": "Introspection",
"summary": "Returns a string describing the stream implementation.",
"examples": [
"(stream/type (stream/byte-buffer)) ;; => \"byte-buffer\"\n(stream/type (stream/from-string \"x\")) ;; => \"string\"\n(stream/type (stream/open-input \"f.txt\")) ;; => \"file-input\"\n(stream/type *stdout*) ;; => \"stdout\""
],
"body": "Returns a string describing the stream implementation.\n\n```sema\n(stream/type (stream/byte-buffer)) ;; => \"byte-buffer\"\n(stream/type (stream/from-string \"x\")) ;; => \"string\"\n(stream/type (stream/open-input \"f.txt\")) ;; => \"file-input\"\n(stream/type *stdout*) ;; => \"stdout\"\n```"
},
{
"name": "stream/write",
"module": "streams",
"section": "Writing",
"summary": "Write a bytevector. Returns the number of bytes written.",
"examples": [
"(stream/write s (bytevector 72 101 108 108 111)) ;; => 5"
],
"body": "Write a bytevector. Returns the number of bytes written.\n\n```sema\n(stream/write s (bytevector 72 101 108 108 111)) ;; => 5\n```"
},
{
"name": "stream/write-byte",
"module": "streams",
"section": "Writing",
"summary": "Write a single byte (integer 0–255).",
"examples": [
"(stream/write-byte s 10) ; write a newline"
],
"body": "Write a single byte (integer 0–255).\n\n```sema\n(stream/write-byte s 10) ; write a newline\n```"
},
{
"name": "stream/write-string",
"module": "streams",
"section": "Writing",
"summary": "Write a string as UTF-8 bytes. Returns the number of bytes written.",
"examples": [
"(stream/write-string s \"hello\") ;; => 5"
],
"body": "Write a string as UTF-8 bytes. Returns the number of bytes written.\n\n```sema\n(stream/write-string s \"hello\") ;; => 5\n```"
},
{
"name": "stream?",
"module": "streams",
"section": "Introspection",
"summary": "Type predicate — returns `#t` if the value is a stream.",
"examples": [
"(stream? (stream/byte-buffer)) ;; => #t\n(stream? 42) ;; => #f"
],
"body": "Type predicate — returns `#t` if the value is a stream.\n\n```sema\n(stream? (stream/byte-buffer)) ;; => #t\n(stream? 42) ;; => #f\n```"
},
{
"name": "with-stream",
"module": "streams",
"section": "Resource Management",
"summary": "Macro that binds a stream, executes the body, and automatically closes the stream on exit — even if an error is thrown.",
"examples": [
"(with-stream (s (stream/open-input \"data.txt\"))\n (stream/read-all s))\n;; s is closed here, even if read-all threw an error\n\n;; Write to a file\n(with-stream (out (stream/open-output \"output.txt\"))\n (stream/write-string out \"line 1\\n\")\n (stream/write-string out \"line 2\\n\"))\n;; file is flushed and closed"
],
"body": "Macro that binds a stream, executes the body, and automatically closes the stream on exit — even if an error is thrown.\n\n```sema\n(with-stream (s (stream/open-input \"data.txt\"))\n (stream/read-all s))\n;; s is closed here, even if read-all threw an error\n\n;; Write to a file\n(with-stream (out (stream/open-output \"output.txt\"))\n (stream/write-string out \"line 1\\n\")\n (stream/write-string out \"line 2\\n\"))\n;; file is flushed and closed\n```"
},
{
"name": "char-ci<=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Case-insensitive character less-than-or-equal (compares the lowercased code points).",
"params": [
{
"name": "a",
"type": "char"
},
{
"name": "b",
"type": "char"
}
],
"returns": "bool",
"examples": [
"(char-ci<=? #\\A #\\a) ; => #t"
],
"body": "Case-insensitive character less-than-or-equal (compares the lowercased code points).\n\n```sema\n(char-ci<=? #\\A #\\a) ; => #t\n```"
},
{
"name": "char-ci<?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Case-insensitive character less-than (compares the lowercased code points).",
"params": [
{
"name": "a",
"type": "char"
},
{
"name": "b",
"type": "char"
}
],
"returns": "bool",
"examples": [
"(char-ci<? #\\A #\\b) ; => #t"
],
"body": "Case-insensitive character less-than (compares the lowercased code points).\n\n```sema\n(char-ci<? #\\A #\\b) ; => #t\n```"
},
{
"name": "char-ci=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Case-insensitive character equality.",
"examples": [
"(char-ci=? #\\A #\\a) ; => #t"
],
"body": "Case-insensitive character equality.\n\n```sema\n(char-ci=? #\\A #\\a) ; => #t\n```"
},
{
"name": "char-ci>=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Case-insensitive character greater-than-or-equal (compares the lowercased code points).",
"params": [
{
"name": "a",
"type": "char"
},
{
"name": "b",
"type": "char"
}
],
"returns": "bool",
"examples": [
"(char-ci>=? #\\B #\\b) ; => #t"
],
"body": "Case-insensitive character greater-than-or-equal (compares the lowercased code points).\n\n```sema\n(char-ci>=? #\\B #\\b) ; => #t\n```"
},
{
"name": "char-ci>?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Case-insensitive character greater-than (compares the lowercased code points).",
"params": [
{
"name": "a",
"type": "char"
},
{
"name": "b",
"type": "char"
}
],
"returns": "bool",
"examples": [
"(char-ci>? #\\B #\\a) ; => #t"
],
"body": "Case-insensitive character greater-than (compares the lowercased code points).\n\n```sema\n(char-ci>? #\\B #\\a) ; => #t\n```"
},
{
"name": "char-lower-case?",
"aliases": [
"char/lower-case?"
],
"module": "strings",
"section": "Characters",
"summary": "Test if a character is lowercase.",
"params": [
{
"name": "c",
"type": "char"
}
],
"returns": "bool",
"examples": [
"(char-lower-case? #\\a) ; => #t\n(char-lower-case? #\\A) ; => #f"
],
"body": "Test if a character is lowercase.\n\n```sema\n(char-lower-case? #\\a) ; => #t\n(char-lower-case? #\\A) ; => #f\n```"
},
{
"name": "char/alphabetic?",
"aliases": [
"char-alphabetic?"
],
"module": "strings",
"section": "Characters",
"summary": "Test if a character is alphabetic.",
"examples": [
"(char/alphabetic? #\\a) ; => #t\n(char/alphabetic? #\\5) ; => #f"
],
"body": "Test if a character is alphabetic.\n\n```sema\n(char/alphabetic? #\\a) ; => #t\n(char/alphabetic? #\\5) ; => #f\n```"
},
{
"name": "char/downcase",
"aliases": [
"char-downcase"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a character to lowercase.",
"examples": [
"(char/downcase #\\Z) ; => #\\z"
],
"body": "Convert a character to lowercase.\n\n```sema\n(char/downcase #\\Z) ; => #\\z\n```"
},
{
"name": "char/numeric?",
"aliases": [
"char-numeric?"
],
"module": "strings",
"section": "Characters",
"summary": "Test if a character is numeric.",
"examples": [
"(char/numeric? #\\5) ; => #t\n(char/numeric? #\\a) ; => #f"
],
"body": "Test if a character is numeric.\n\n```sema\n(char/numeric? #\\5) ; => #t\n(char/numeric? #\\a) ; => #f\n```"
},
{
"name": "char/to-integer",
"aliases": [
"char->integer"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a character to its Unicode code point.",
"examples": [
"(char/to-integer #\\A) ; => 65\n(char/to-integer #\\a) ; => 97"
],
"body": "Convert a character to its Unicode code point.\n\n```sema\n(char/to-integer #\\A) ; => 65\n(char/to-integer #\\a) ; => 97\n```"
},
{
"name": "char/to-string",
"aliases": [
"char->string"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a character to a single-character string.",
"examples": [
"(char/to-string #\\a) ; => \"a\""
],
"body": "Convert a character to a single-character string.\n\n```sema\n(char/to-string #\\a) ; => \"a\"\n```"
},
{
"name": "char/upcase",
"aliases": [
"char-upcase"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a character to uppercase.",
"examples": [
"(char/upcase #\\a) ; => #\\A"
],
"body": "Convert a character to uppercase.\n\n```sema\n(char/upcase #\\a) ; => #\\A\n```"
},
{
"name": "char/upper-case?",
"aliases": [
"char-upper-case?"
],
"module": "strings",
"section": "Characters",
"summary": "Test if a character is uppercase.",
"examples": [
"(char/upper-case? #\\A) ; => #t\n(char/upper-case? #\\a) ; => #f"
],
"body": "Test if a character is uppercase.\n\n```sema\n(char/upper-case? #\\A) ; => #t\n(char/upper-case? #\\a) ; => #f\n```"
},
{
"name": "char/whitespace?",
"aliases": [
"char-whitespace?"
],
"module": "strings",
"section": "Characters",
"summary": "Test if a character is whitespace.",
"examples": [
"(char/whitespace? #\\space) ; => #t\n(char/whitespace? #\\a) ; => #f"
],
"body": "Test if a character is whitespace.\n\n```sema\n(char/whitespace? #\\space) ; => #t\n(char/whitespace? #\\a) ; => #f\n```"
},
{
"name": "char<=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Character less-than-or-equal.",
"examples": [
"(char<=? #\\a #\\b) ; => #t\n(char<=? #\\a #\\a) ; => #t"
],
"body": "Character less-than-or-equal.\n\n```sema\n(char<=? #\\a #\\b) ; => #t\n(char<=? #\\a #\\a) ; => #t\n```"
},
{
"name": "char<?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Character less-than (by code point).",
"examples": [
"(char<? #\\a #\\b) ; => #t"
],
"body": "Character less-than (by code point).\n\n```sema\n(char<? #\\a #\\b) ; => #t\n```"
},
{
"name": "char=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Character equality.",
"examples": [
"(char=? #\\a #\\a) ; => #t\n(char=? #\\a #\\b) ; => #f"
],
"body": "Character equality.\n\n```sema\n(char=? #\\a #\\a) ; => #t\n(char=? #\\a #\\b) ; => #f\n```"
},
{
"name": "char>=?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Character greater-than-or-equal.",
"examples": [
"(char>=? #\\b #\\a) ; => #t"
],
"body": "Character greater-than-or-equal.\n\n```sema\n(char>=? #\\b #\\a) ; => #t\n```"
},
{
"name": "char>?",
"module": "strings",
"section": "Character Comparison (R7RS)",
"summary": "Character greater-than.",
"examples": [
"(char>? #\\b #\\a) ; => #t"
],
"body": "Character greater-than.\n\n```sema\n(char>? #\\b #\\a) ; => #t\n```"
},
{
"name": "format",
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Format a string with `~a` placeholders.",
"examples": [
"(format \"~a is ~a\" \"Sema\" \"great\") ; => \"Sema is great\"\n(format \"~a + ~a = ~a\" 1 2 3) ; => \"1 + 2 = 3\""
],
"body": "Format a string with `~a` placeholders.\n\n```sema\n(format \"~a is ~a\" \"Sema\" \"great\") ; => \"Sema is great\"\n(format \"~a + ~a = ~a\" 1 2 3) ; => \"1 + 2 = 3\"\n```"
},
{
"name": "integer/to-char",
"aliases": [
"integer->char"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a Unicode code point to a character.",
"examples": [
"(integer/to-char 65) ; => #\\A\n(integer/to-char 955) ; => #\\λ"
],
"body": "Convert a Unicode code point to a character.\n\n```sema\n(integer/to-char 65) ; => #\\A\n(integer/to-char 955) ; => #\\λ\n```"
},
{
"name": "keyword/to-string",
"aliases": [
"keyword->string"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a keyword to a string.",
"examples": [
"(keyword/to-string :name) ; => \"name\""
],
"body": "Convert a keyword to a string.\n\n```sema\n(keyword/to-string :name) ; => \"name\"\n```"
},
{
"name": "list->string",
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a list of characters to a string.",
"examples": [
"(list->string '(#\\h #\\i)) ; => \"hi\""
],
"body": "Convert a list of characters to a string.\n\n```sema\n(list->string '(#\\h #\\i)) ; => \"hi\"\n```"
},
{
"name": "make-string",
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Repeat a string `n` times. In Sema, `make-string` is an alias for `string/repeat`, so it takes a string to repeat and a count (rather than the R7RS `(make-string k char)` signature).",
"params": [
{
"name": "s",
"type": "string"
},
{
"name": "n",
"type": "int"
}
],
"returns": "string",
"examples": [
"(make-string \"ab\" 3) ; => \"ababab\""
],
"body": "Repeat a string `n` times. In Sema, `make-string` is an alias for `string/repeat`, so it takes a string to repeat and a count (rather than the R7RS `(make-string k char)` signature).\n\n```sema\n(make-string \"ab\" 3) ; => \"ababab\"\n```"
},
{
"name": "number/to-string",
"aliases": [
"number->string"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a number to a string.",
"examples": [
"(number/to-string 42) ; => \"42\"\n(number/to-string 3.14) ; => \"3.14\""
],
"body": "Convert a number to a string.\n\n```sema\n(number/to-string 42) ; => \"42\"\n(number/to-string 3.14) ; => \"3.14\"\n```"
},
{
"name": "str",
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Convert any value to its string representation.",
"examples": [
"(str 42) ; => \"42\"\n(str #t) ; => \"#t\"\n(str '(1 2 3)) ; => \"(1 2 3)\""
],
"body": "Convert any value to its string representation.\n\n```sema\n(str 42) ; => \"42\"\n(str #t) ; => \"#t\"\n(str '(1 2 3)) ; => \"(1 2 3)\"\n```"
},
{
"name": "string->float",
"aliases": [
"string/to-float"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Parse a string as a float. Integers and floats are accepted directly and returned as a float. Raises an error when a string cannot be parsed.",
"params": [
{
"name": "x",
"type": "string | number"
}
],
"returns": "float",
"examples": [
"(string->float \"3.14\") ; => 3.14\n(string->float \"42\") ; => 42.0\n(string->float 7) ; => 7.0"
],
"body": "Parse a string as a float. Integers and floats are accepted directly and returned as a float. Raises an error when a string cannot be parsed.\n\n```sema\n(string->float \"3.14\") ; => 3.14\n(string->float \"42\") ; => 42.0\n(string->float 7) ; => 7.0\n```"
},
{
"name": "string-ci=?",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Case-insensitive string equality comparison. Compares two strings after applying case folding to both.",
"examples": [
"(string-ci=? \"Hello\" \"hello\") ; => #t\n(string-ci=? \"ABC\" \"abc\") ; => #t\n(string-ci=? \"CAFÉ\" \"café\") ; => #t\n(string-ci=? \"hello\" \"world\") ; => #f"
],
"body": "Case-insensitive string equality comparison. Compares two strings after applying case folding to both.\n\n```sema\n(string-ci=? \"Hello\" \"hello\") ; => #t\n(string-ci=? \"ABC\" \"abc\") ; => #t\n(string-ci=? \"CAFÉ\" \"café\") ; => #t\n(string-ci=? \"hello\" \"world\") ; => #f\n```"
},
{
"name": "string/after",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Everything after the first occurrence of a needle. Returns the original string if needle not found.",
"examples": [
"(string/after \"hello@world.com\" \"@\") ; => \"world.com\"\n(string/after \"no-match\" \"@\") ; => \"no-match\""
],
"body": "Everything after the first occurrence of a needle. Returns the original string if needle not found.\n\n```sema\n(string/after \"hello@world.com\" \"@\") ; => \"world.com\"\n(string/after \"no-match\" \"@\") ; => \"no-match\"\n```"
},
{
"name": "string/after-last",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Everything after the last occurrence of a needle.",
"examples": [
"(string/after-last \"a.b.c\" \".\") ; => \"c\""
],
"body": "Everything after the last occurrence of a needle.\n\n```sema\n(string/after-last \"a.b.c\" \".\") ; => \"c\"\n```"
},
{
"name": "string/append",
"aliases": [
"string-append"
],
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Concatenate strings together.",
"examples": [
"(string/append \"hello\" \" \" \"world\") ; => \"hello world\"\n(string/append \"a\" \"b\" \"c\") ; => \"abc\""
],
"body": "Concatenate strings together.\n\n```sema\n(string/append \"hello\" \" \" \"world\") ; => \"hello world\"\n(string/append \"a\" \"b\" \"c\") ; => \"abc\"\n```"
},
{
"name": "string/before",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Everything before the first occurrence of a needle.",
"examples": [
"(string/before \"hello@world.com\" \"@\") ; => \"hello\"\n(string/before \"no-match\" \"@\") ; => \"no-match\""
],
"body": "Everything before the first occurrence of a needle.\n\n```sema\n(string/before \"hello@world.com\" \"@\") ; => \"hello\"\n(string/before \"no-match\" \"@\") ; => \"no-match\"\n```"
},
{
"name": "string/before-last",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Everything before the last occurrence of a needle.",
"examples": [
"(string/before-last \"a.b.c\" \".\") ; => \"a.b\""
],
"body": "Everything before the last occurrence of a needle.\n\n```sema\n(string/before-last \"a.b.c\" \".\") ; => \"a.b\"\n```"
},
{
"name": "string/between",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Extract the portion between two delimiters.",
"examples": [
"(string/between \"[hello]\" \"[\" \"]\") ; => \"hello\"\n(string/between \"start:middle:end\" \"start:\" \":end\") ; => \"middle\""
],
"body": "Extract the portion between two delimiters.\n\n```sema\n(string/between \"[hello]\" \"[\" \"]\") ; => \"hello\"\n(string/between \"start:middle:end\" \"start:\" \":end\") ; => \"middle\"\n```"
},
{
"name": "string/byte-length",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Return the UTF-8 byte length of a string (as opposed to character count from `string/length`). Useful for understanding the actual memory footprint — emoji and CJK characters use more bytes than ASCII.",
"examples": [
"(string/byte-length \"hello\") ; => 5 (ASCII: 1 byte each)\n(string/byte-length \"héllo\") ; => 6 (é is 2 bytes in UTF-8)\n(string/byte-length \"日本語\") ; => 9 (CJK: 3 bytes each)\n(string/byte-length \"😀\") ; => 4 (emoji: 4 bytes)",
"(string/length \"😀\") ; => 1 (one character)\n(string/byte-length \"😀\") ; => 4 (four bytes)"
],
"body": "Return the UTF-8 byte length of a string (as opposed to character count from `string/length`). Useful for understanding the actual memory footprint — emoji and CJK characters use more bytes than ASCII.\n\n```sema\n(string/byte-length \"hello\") ; => 5 (ASCII: 1 byte each)\n(string/byte-length \"héllo\") ; => 6 (é is 2 bytes in UTF-8)\n(string/byte-length \"日本語\") ; => 9 (CJK: 3 bytes each)\n(string/byte-length \"😀\") ; => 4 (emoji: 4 bytes)\n```\n\nCompare with `string/length` which counts characters:\n\n```sema\n(string/length \"😀\") ; => 1 (one character)\n(string/byte-length \"😀\") ; => 4 (four bytes)\n```"
},
{
"name": "string/camel-case",
"module": "strings",
"section": "Case Conversion",
"summary": "Convert to camelCase.",
"examples": [
"(string/camel-case \"hello_world\") ; => \"helloWorld\"\n(string/camel-case \"Hello World\") ; => \"helloWorld\""
],
"body": "Convert to camelCase.\n\n```sema\n(string/camel-case \"hello_world\") ; => \"helloWorld\"\n(string/camel-case \"Hello World\") ; => \"helloWorld\"\n```"
},
{
"name": "string/capitalize",
"module": "strings",
"section": "Core String Operations",
"summary": "Capitalize the first character.",
"examples": [
"(string/capitalize \"hello\") ; => \"Hello\""
],
"body": "Capitalize the first character.\n\n```sema\n(string/capitalize \"hello\") ; => \"Hello\"\n```"
},
{
"name": "string/chars",
"module": "strings",
"section": "Core String Operations",
"summary": "Convert a string to a list of characters.",
"examples": [
"(string/chars \"abc\") ; => (#\\a #\\b #\\c)"
],
"body": "Convert a string to a list of characters.\n\n```sema\n(string/chars \"abc\") ; => (#\\a #\\b #\\c)\n```"
},
{
"name": "string/chop-end",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Remove a suffix if present.",
"examples": [
"(string/chop-end \"file.txt\" \".txt\") ; => \"file\"\n(string/chop-end \"file.txt\" \".md\") ; => \"file.txt\""
],
"body": "Remove a suffix if present.\n\n```sema\n(string/chop-end \"file.txt\" \".txt\") ; => \"file\"\n(string/chop-end \"file.txt\" \".md\") ; => \"file.txt\"\n```"
},
{
"name": "string/chop-start",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Remove a prefix if present, otherwise return unchanged.",
"examples": [
"(string/chop-start \"Hello World\" \"Hello \") ; => \"World\"\n(string/chop-start \"Hello\" \"Bye\") ; => \"Hello\""
],
"body": "Remove a prefix if present, otherwise return unchanged.\n\n```sema\n(string/chop-start \"Hello World\" \"Hello \") ; => \"World\"\n(string/chop-start \"Hello\" \"Bye\") ; => \"Hello\"\n```"
},
{
"name": "string/codepoints",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Return a list of Unicode codepoint integers for each character in a string. This reveals the internal structure of composed characters and emoji sequences.",
"examples": [
"(string/codepoints \"ABC\") ; => (65 66 67)\n(string/codepoints \"é\") ; => (233)\n(string/codepoints \"😀\") ; => (128512)",
";; 👨👩👦 is actually 👨 + ZWJ + 👩 + ZWJ + 👦\n(string/codepoints \"👨👩👦\") ; => (128104 8205 128105 8205 128102)\n\n;; 👋🏽 is 👋 + skin tone modifier\n(string/codepoints \"👋🏽\") ; => (128075 127997)"
],
"body": "Return a list of Unicode codepoint integers for each character in a string. This reveals the internal structure of composed characters and emoji sequences.\n\n```sema\n(string/codepoints \"ABC\") ; => (65 66 67)\n(string/codepoints \"é\") ; => (233)\n(string/codepoints \"😀\") ; => (128512)\n```\n\nEmoji that appear as a single glyph are often multiple codepoints joined by Zero Width Joiner (U+200D = 8205):\n\n```sema\n;; 👨👩👦 is actually 👨 + ZWJ + 👩 + ZWJ + 👦\n(string/codepoints \"👨👩👦\") ; => (128104 8205 128105 8205 128102)\n\n;; 👋🏽 is 👋 + skin tone modifier\n(string/codepoints \"👋🏽\") ; => (128075 127997)\n```"
},
{
"name": "string/contains?",
"module": "strings",
"section": "Core String Operations",
"summary": "Test if a string contains a substring.",
"examples": [
"(string/contains? \"hello\" \"ell\") ; => #t\n(string/contains? \"hello\" \"xyz\") ; => #f"
],
"body": "Test if a string contains a substring.\n\n```sema\n(string/contains? \"hello\" \"ell\") ; => #t\n(string/contains? \"hello\" \"xyz\") ; => #f\n```"
},
{
"name": "string/empty?",
"module": "strings",
"section": "Core String Operations",
"summary": "Test if a string is empty.",
"examples": [
"(string/empty? \"\") ; => #t\n(string/empty? \"hello\") ; => #f"
],
"body": "Test if a string is empty.\n\n```sema\n(string/empty? \"\") ; => #t\n(string/empty? \"hello\") ; => #f\n```"
},
{
"name": "string/ends-with?",
"module": "strings",
"section": "Core String Operations",
"summary": "Test if a string ends with a suffix.",
"examples": [
"(string/ends-with? \"hello\" \"lo\") ; => #t\n(string/ends-with? \"hello\" \"he\") ; => #f"
],
"body": "Test if a string ends with a suffix.\n\n```sema\n(string/ends-with? \"hello\" \"lo\") ; => #t\n(string/ends-with? \"hello\" \"he\") ; => #f\n```"
},
{
"name": "string/ensure-end",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Ensure a string ends with a suffix.",
"examples": [
"(string/ensure-end \"path\" \"/\") ; => \"path/\"\n(string/ensure-end \"path/\" \"/\") ; => \"path/\""
],
"body": "Ensure a string ends with a suffix.\n\n```sema\n(string/ensure-end \"path\" \"/\") ; => \"path/\"\n(string/ensure-end \"path/\" \"/\") ; => \"path/\"\n```"
},
{
"name": "string/ensure-start",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Ensure a string starts with a prefix (adds it if missing).",
"examples": [
"(string/ensure-start \"/path\" \"/\") ; => \"/path\"\n(string/ensure-start \"path\" \"/\") ; => \"/path\""
],
"body": "Ensure a string starts with a prefix (adds it if missing).\n\n```sema\n(string/ensure-start \"/path\" \"/\") ; => \"/path\"\n(string/ensure-start \"path\" \"/\") ; => \"/path\"\n```"
},
{
"name": "string/foldcase",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Apply Unicode case folding to a string. Useful for case-insensitive comparisons and normalization. Uses full Unicode-aware lowercasing.",
"examples": [
"(string/foldcase \"HELLO\") ; => \"hello\"\n(string/foldcase \"Hello World\") ; => \"hello world\"\n(string/foldcase \"Straße\") ; => \"straße\"\n(string/foldcase \"ΩΜΕΓΑ\") ; => \"ωμεγα\""
],
"body": "Apply Unicode case folding to a string. Useful for case-insensitive comparisons and normalization. Uses full Unicode-aware lowercasing.\n\n```sema\n(string/foldcase \"HELLO\") ; => \"hello\"\n(string/foldcase \"Hello World\") ; => \"hello world\"\n(string/foldcase \"Straße\") ; => \"straße\"\n(string/foldcase \"ΩΜΕΓΑ\") ; => \"ωμεγα\"\n```"
},
{
"name": "string/from-codepoints",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Construct a string from a list of Unicode codepoint integers. This is the inverse of `string/codepoints` and enables building emoji programmatically by combining codepoints.",
"examples": [
"(string/from-codepoints (list 65 66 67)) ; => \"ABC\"\n(string/from-codepoints (list 233)) ; => \"é\"",
";; Build a family: 👨 + ZWJ + 👩 + ZWJ + 👧\n(string/from-codepoints (list 128104 8205 128105 8205 128103))\n;; => 👨👩👧\n\n;; Build a profession: 👩 + ZWJ + 💻\n(string/from-codepoints (list 128105 8205 128187))\n;; => 👩💻\n\n;; Add skin tone: 👋 + modifier\n(string/from-codepoints (list 128075 127997))\n;; => 👋🏽\n\n;; Build flags from Regional Indicators (A=127462):\n(string/from-codepoints (list 127475 127476))\n;; => 🇳🇴 (NO = Norway)",
"(string/from-codepoints (string/codepoints \"Hello 世界\"))\n;; => \"Hello 世界\""
],
"body": "Construct a string from a list of Unicode codepoint integers. This is the inverse of `string/codepoints` and enables building emoji programmatically by combining codepoints.\n\n```sema\n(string/from-codepoints (list 65 66 67)) ; => \"ABC\"\n(string/from-codepoints (list 233)) ; => \"é\"\n```\n\nBuild emoji by combining people with ZWJ (8205):\n\n```sema\n;; Build a family: 👨 + ZWJ + 👩 + ZWJ + 👧\n(string/from-codepoints (list 128104 8205 128105 8205 128103))\n;; => 👨👩👧\n\n;; Build a profession: 👩 + ZWJ + 💻\n(string/from-codepoints (list 128105 8205 128187))\n;; => 👩💻\n\n;; Add skin tone: 👋 + modifier\n(string/from-codepoints (list 128075 127997))\n;; => 👋🏽\n\n;; Build flags from Regional Indicators (A=127462):\n(string/from-codepoints (list 127475 127476))\n;; => 🇳🇴 (NO = Norway)\n```\n\nRoundtrip any string through codepoints:\n\n```sema\n(string/from-codepoints (string/codepoints \"Hello 世界\"))\n;; => \"Hello 世界\"\n```"
},
{
"name": "string/headline",
"module": "strings",
"section": "Case Conversion",
"summary": "Convert to Title Case headline.",
"examples": [
"(string/headline \"hello_world\") ; => \"Hello World\"\n(string/headline \"helloWorld\") ; => \"Hello World\""
],
"body": "Convert to Title Case headline.\n\n```sema\n(string/headline \"hello_world\") ; => \"Hello World\"\n(string/headline \"helloWorld\") ; => \"Hello World\"\n```"
},
{
"name": "string/index-of",
"module": "strings",
"section": "Core String Operations",
"summary": "Return the character index of the first occurrence of a substring, or `nil` if not found.",
"examples": [
"(string/index-of \"hello\" \"ll\") ; => 2\n(string/index-of \"hello\" \"xyz\") ; => nil"
],
"body": "Return the character index of the first occurrence of a substring, or `nil` if not found.\n\n```sema\n(string/index-of \"hello\" \"ll\") ; => 2\n(string/index-of \"hello\" \"xyz\") ; => nil\n```"
},
{
"name": "string/intern",
"module": "strings",
"section": "Core String Operations",
"summary": "Intern a string, returning a shared instance for equal contents. Repeated calls with equal strings return values backed by the same underlying storage, which can reduce memory for many duplicated strings.",
"params": [
{
"name": "s",
"type": "string"
}
],
"returns": "string",
"examples": [
"(string/intern \"hello\") ; => \"hello\""
],
"body": "Intern a string, returning a shared instance for equal contents. Repeated calls with equal strings return values backed by the same underlying storage, which can reduce memory for many duplicated strings.\n\n```sema\n(string/intern \"hello\") ; => \"hello\"\n```"
},
{
"name": "string/join",
"aliases": [
"string-join"
],
"module": "strings",
"section": "Core String Operations",
"summary": "Join a list of strings with a separator.",
"examples": [
"(string/join '(\"a\" \"b\" \"c\") \", \") ; => \"a, b, c\"\n(string/join '(\"x\" \"y\") \"-\") ; => \"x-y\""
],
"body": "Join a list of strings with a separator.\n\n```sema\n(string/join '(\"a\" \"b\" \"c\") \", \") ; => \"a, b, c\"\n(string/join '(\"x\" \"y\") \"-\") ; => \"x-y\"\n```"
},
{
"name": "string/kebab-case",
"module": "strings",
"section": "Case Conversion",
"summary": "Convert to kebab-case.",
"examples": [
"(string/kebab-case \"helloWorld\") ; => \"hello-world\"\n(string/kebab-case \"Hello World\") ; => \"hello-world\""
],
"body": "Convert to kebab-case.\n\n```sema\n(string/kebab-case \"helloWorld\") ; => \"hello-world\"\n(string/kebab-case \"Hello World\") ; => \"hello-world\"\n```"
},
{
"name": "string/last-index-of",
"module": "strings",
"section": "Core String Operations",
"summary": "Find the last occurrence of a substring. Returns the character index or `nil` if not found.",
"examples": [
"(string/last-index-of \"abcabc\" \"abc\") ; => 3\n(string/last-index-of \"hello\" \"xyz\") ; => nil"
],
"body": "Find the last occurrence of a substring. Returns the character index or `nil` if not found.\n\n```sema\n(string/last-index-of \"abcabc\" \"abc\") ; => 3\n(string/last-index-of \"hello\" \"xyz\") ; => nil\n```"
},
{
"name": "string/length",
"aliases": [
"string-length"
],
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Return the number of characters in a string.",
"examples": [
"(string/length \"hello\") ; => 5\n(string/length \"\") ; => 0\n(string/length \"héllo\") ; => 5\n(string/length \"日本語\") ; => 3"
],
"body": "Return the number of characters in a string.\n\n```sema\n(string/length \"hello\") ; => 5\n(string/length \"\") ; => 0\n(string/length \"héllo\") ; => 5\n(string/length \"日本語\") ; => 3\n```"
},
{
"name": "string/lower",
"aliases": [
"string-downcase"
],
"module": "strings",
"section": "Core String Operations",
"summary": "Convert string to lowercase.",
"examples": [
"(string/lower \"HELLO\") ; => \"hello\""
],
"body": "Convert string to lowercase.\n\n```sema\n(string/lower \"HELLO\") ; => \"hello\"\n```"
},
{
"name": "string/map",
"module": "strings",
"section": "Core String Operations",
"summary": "Apply a character function to each character in a string, returning a new string.",
"examples": [
"(string/map char/upcase \"hello\") ; => \"HELLO\""
],
"body": "Apply a character function to each character in a string, returning a new string.\n\n```sema\n(string/map char/upcase \"hello\") ; => \"HELLO\"\n```"
},
{
"name": "string/normalize",
"module": "strings",
"section": "Unicode & Encoding",
"summary": "Normalize a string to a Unicode normalization form. Supported forms: `:nfc`, `:nfd`, `:nfkc`, `:nfkd` (as keywords or strings).",
"examples": [
";; NFC: combine decomposed characters\n;; e + combining acute accent → é\n(string/normalize \"e\\u0301\" :nfc) ; => \"é\"\n\n;; NFD: decompose composed characters\n(string/length (string/normalize \"é\" :nfd)) ; => 2 (e + combining accent)\n\n;; NFKC/NFKD: compatibility decomposition (ligatures, etc.)\n(string/normalize \"\\uFB01\" :nfkc) ; => \"fi\" (fi ligature → two letters)\n\n;; String form names also work\n(string/normalize \"e\\u0301\" \"NFC\") ; => \"é\""
],
"body": "Normalize a string to a Unicode normalization form. Supported forms: `:nfc`, `:nfd`, `:nfkc`, `:nfkd` (as keywords or strings).\n\n- **NFC** — Canonical Decomposition, followed by Canonical Composition (most common)\n- **NFD** — Canonical Decomposition\n- **NFKC** — Compatibility Decomposition, followed by Canonical Composition\n- **NFKD** — Compatibility Decomposition\n\n```sema\n;; NFC: combine decomposed characters\n;; e + combining acute accent → é\n(string/normalize \"e\\u0301\" :nfc) ; => \"é\"\n\n;; NFD: decompose composed characters\n(string/length (string/normalize \"é\" :nfd)) ; => 2 (e + combining accent)\n\n;; NFKC/NFKD: compatibility decomposition (ligatures, etc.)\n(string/normalize \"\\uFB01\" :nfkc) ; => \"fi\" (fi ligature → two letters)\n\n;; String form names also work\n(string/normalize \"e\\u0301\" \"NFC\") ; => \"é\"\n```"
},
{
"name": "string/number?",
"module": "strings",
"section": "Core String Operations",
"summary": "Test if a string represents a valid number.",
"examples": [
"(string/number? \"42\") ; => #t\n(string/number? \"3.14\") ; => #t\n(string/number? \"hello\") ; => #f"
],
"body": "Test if a string represents a valid number.\n\n```sema\n(string/number? \"42\") ; => #t\n(string/number? \"3.14\") ; => #t\n(string/number? \"hello\") ; => #f\n```"
},
{
"name": "string/pad-left",
"module": "strings",
"section": "Core String Operations",
"summary": "Pad a string on the left to a given width.",
"examples": [
"(string/pad-left \"42\" 5 \"0\") ; => \"00042\"\n(string/pad-left \"hi\" 5) ; => \" hi\""
],
"body": "Pad a string on the left to a given width.\n\n```sema\n(string/pad-left \"42\" 5 \"0\") ; => \"00042\"\n(string/pad-left \"hi\" 5) ; => \" hi\"\n```"
},
{
"name": "string/pad-right",
"module": "strings",
"section": "Core String Operations",
"summary": "Pad a string on the right to a given width.",
"examples": [
"(string/pad-right \"hi\" 5) ; => \"hi \"\n(string/pad-right \"42\" 5 \"0\") ; => \"42000\""
],
"body": "Pad a string on the right to a given width.\n\n```sema\n(string/pad-right \"hi\" 5) ; => \"hi \"\n(string/pad-right \"42\" 5 \"0\") ; => \"42000\"\n```"
},
{
"name": "string/pascal-case",
"module": "strings",
"section": "Case Conversion",
"summary": "Convert to PascalCase.",
"examples": [
"(string/pascal-case \"hello_world\") ; => \"HelloWorld\"\n(string/pascal-case \"hello world\") ; => \"HelloWorld\""
],
"body": "Convert to PascalCase.\n\n```sema\n(string/pascal-case \"hello_world\") ; => \"HelloWorld\"\n(string/pascal-case \"hello world\") ; => \"HelloWorld\"\n```"
},
{
"name": "string/ref",
"aliases": [
"string-ref"
],
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Return the character at a given index.",
"examples": [
"(string/ref \"hello\" 0) ; => #\\h\n(string/ref \"hello\" 4) ; => #\\o"
],
"body": "Return the character at a given index.\n\n```sema\n(string/ref \"hello\" 0) ; => #\\h\n(string/ref \"hello\" 4) ; => #\\o\n```"
},
{
"name": "string/remove",
"module": "strings",
"section": "Replacement",
"summary": "Remove all occurrences of a substring.",
"examples": [
"(string/remove \"hello world\" \"o\") ; => \"hell wrld\""
],
"body": "Remove all occurrences of a substring.\n\n```sema\n(string/remove \"hello world\" \"o\") ; => \"hell wrld\"\n```"
},
{
"name": "string/repeat",
"module": "strings",
"section": "Core String Operations",
"summary": "Repeat a string N times.",
"examples": [
"(string/repeat \"ab\" 3) ; => \"ababab\"\n(string/repeat \"-\" 5) ; => \"-----\""
],
"body": "Repeat a string N times.\n\n```sema\n(string/repeat \"ab\" 3) ; => \"ababab\"\n(string/repeat \"-\" 5) ; => \"-----\"\n```"
},
{
"name": "string/replace",
"module": "strings",
"section": "Core String Operations",
"summary": "Replace all occurrences of a substring.",
"examples": [
"(string/replace \"hello\" \"l\" \"r\") ; => \"herro\"\n(string/replace \"aaa\" \"a\" \"b\") ; => \"bbb\""
],
"body": "Replace all occurrences of a substring.\n\n```sema\n(string/replace \"hello\" \"l\" \"r\") ; => \"herro\"\n(string/replace \"aaa\" \"a\" \"b\") ; => \"bbb\"\n```"
},
{
"name": "string/replace-first",
"module": "strings",
"section": "Replacement",
"summary": "Replace only the first occurrence of a substring.",
"examples": [
"(string/replace-first \"aaa\" \"a\" \"b\") ; => \"baa\""
],
"body": "Replace only the first occurrence of a substring.\n\n```sema\n(string/replace-first \"aaa\" \"a\" \"b\") ; => \"baa\"\n```"
},
{
"name": "string/replace-last",
"module": "strings",
"section": "Replacement",
"summary": "Replace only the last occurrence.",
"examples": [
"(string/replace-last \"aaa\" \"a\" \"b\") ; => \"aab\""
],
"body": "Replace only the last occurrence.\n\n```sema\n(string/replace-last \"aaa\" \"a\" \"b\") ; => \"aab\"\n```"
},
{
"name": "string/reverse",
"module": "strings",
"section": "Core String Operations",
"summary": "Reverse a string.",
"examples": [
"(string/reverse \"hello\") ; => \"olleh\""
],
"body": "Reverse a string.\n\n```sema\n(string/reverse \"hello\") ; => \"olleh\"\n```"
},
{
"name": "string/slice",
"aliases": [
"substring"
],
"module": "strings",
"section": "Scheme Compatibility Aliases",
"summary": "Extract a substring by start and end character index.",
"examples": [
"(string/slice \"hello\" 1 3) ; => \"el\"\n(string/slice \"hello\" 0 5) ; => \"hello\"\n(string/slice \"héllo\" 1 2) ; => \"é\""
],
"body": "Extract a substring by start and end character index.\n\n```sema\n(string/slice \"hello\" 1 3) ; => \"el\"\n(string/slice \"hello\" 0 5) ; => \"hello\"\n(string/slice \"héllo\" 1 2) ; => \"é\"\n```"
},
{
"name": "string/snake-case",
"module": "strings",
"section": "Case Conversion",
"summary": "Convert to snake_case.",
"examples": [
"(string/snake-case \"helloWorld\") ; => \"hello_world\"\n(string/snake-case \"Hello World\") ; => \"hello_world\""
],
"body": "Convert to snake_case.\n\n```sema\n(string/snake-case \"helloWorld\") ; => \"hello_world\"\n(string/snake-case \"Hello World\") ; => \"hello_world\"\n```"
},
{
"name": "string/split",
"aliases": [
"string-split"
],
"module": "strings",
"section": "Core String Operations",
"summary": "Split a string by a delimiter.",
"examples": [
"(string/split \"a,b,c\" \",\") ; => (\"a\" \"b\" \"c\")\n(string/split \"hello world\" \" \") ; => (\"hello\" \"world\")"
],
"body": "Split a string by a delimiter.\n\n```sema\n(string/split \"a,b,c\" \",\") ; => (\"a\" \"b\" \"c\")\n(string/split \"hello world\" \" \") ; => (\"hello\" \"world\")\n```"
},
{
"name": "string/starts-with?",
"module": "strings",
"section": "Core String Operations",
"summary": "Test if a string starts with a prefix.",
"examples": [
"(string/starts-with? \"hello\" \"he\") ; => #t\n(string/starts-with? \"hello\" \"lo\") ; => #f"
],
"body": "Test if a string starts with a prefix.\n\n```sema\n(string/starts-with? \"hello\" \"he\") ; => #t\n(string/starts-with? \"hello\" \"lo\") ; => #f\n```"
},
{
"name": "string/take",
"module": "strings",
"section": "Slicing & Extraction",
"summary": "Take the first N characters (positive) or last N characters (negative).",
"examples": [
"(string/take \"hello\" 3) ; => \"hel\"\n(string/take \"hello\" -2) ; => \"lo\""
],
"body": "Take the first N characters (positive) or last N characters (negative).\n\n```sema\n(string/take \"hello\" 3) ; => \"hel\"\n(string/take \"hello\" -2) ; => \"lo\"\n```"
},
{
"name": "string/title-case",
"module": "strings",
"section": "Core String Operations",
"summary": "Capitalize the first character of each word.",
"examples": [
"(string/title-case \"hello world\") ; => \"Hello World\""
],
"body": "Capitalize the first character of each word.\n\n```sema\n(string/title-case \"hello world\") ; => \"Hello World\"\n```"
},
{
"name": "string/to-char",
"aliases": [
"string->char"
],
"module": "strings",
"section": "Characters",
"summary": "Convert a single-character string to a character.",
"examples": [
"(string/to-char \"a\") ; => #\\a"
],
"body": "Convert a single-character string to a character.\n\n```sema\n(string/to-char \"a\") ; => #\\a\n```"
},
{
"name": "string/to-keyword",
"aliases": [
"string->keyword"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a string to a keyword.",
"examples": [
"(string/to-keyword \"name\") ; => :name"
],
"body": "Convert a string to a keyword.\n\n```sema\n(string/to-keyword \"name\") ; => :name\n```"
},
{
"name": "string/to-list",
"aliases": [
"string->list"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a string to a list of characters.",
"examples": [
"(string/to-list \"abc\") ; => (#\\a #\\b #\\c)"
],
"body": "Convert a string to a list of characters.\n\n```sema\n(string/to-list \"abc\") ; => (#\\a #\\b #\\c)\n```"
},
{
"name": "string/to-number",
"aliases": [
"string->number"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Parse a string as a number.",
"examples": [
"(string/to-number \"42\") ; => 42\n(string/to-number \"3.14\") ; => 3.14"
],
"body": "Parse a string as a number.\n\n```sema\n(string/to-number \"42\") ; => 42\n(string/to-number \"3.14\") ; => 3.14\n```"
},
{
"name": "string/to-symbol",
"aliases": [
"string->symbol"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a string to a symbol.",
"examples": [
"(string/to-symbol \"foo\") ; => foo"
],
"body": "Convert a string to a symbol.\n\n```sema\n(string/to-symbol \"foo\") ; => foo\n```"
},
{
"name": "string/trim",
"aliases": [
"string-trim"
],
"module": "strings",
"section": "Core String Operations",
"summary": "Remove whitespace from both ends.",
"examples": [
"(string/trim \" hello \") ; => \"hello\"\n(string/trim \"\\thello\\n\") ; => \"hello\""
],
"body": "Remove whitespace from both ends.\n\n```sema\n(string/trim \" hello \") ; => \"hello\"\n(string/trim \"\\thello\\n\") ; => \"hello\"\n```"
},
{
"name": "string/trim-left",
"module": "strings",
"section": "Core String Operations",
"summary": "Remove whitespace from the left.",
"examples": [
"(string/trim-left \" hi\") ; => \"hi\""
],
"body": "Remove whitespace from the left.\n\n```sema\n(string/trim-left \" hi\") ; => \"hi\"\n```"
},
{
"name": "string/trim-right",
"module": "strings",
"section": "Core String Operations",
"summary": "Remove whitespace from the right.",
"examples": [
"(string/trim-right \"hi \") ; => \"hi\""
],
"body": "Remove whitespace from the right.\n\n```sema\n(string/trim-right \"hi \") ; => \"hi\"\n```"
},
{
"name": "string/unwrap",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Remove surrounding delimiters if both present.",
"examples": [
"(string/unwrap \"(hello)\" \"(\" \")\") ; => \"hello\"\n(string/unwrap \"hello\" \"(\" \")\") ; => \"hello\""
],
"body": "Remove surrounding delimiters if both present.\n\n```sema\n(string/unwrap \"(hello)\" \"(\" \")\") ; => \"hello\"\n(string/unwrap \"hello\" \"(\" \")\") ; => \"hello\"\n```"
},
{
"name": "string/upper",
"aliases": [
"string-upcase"
],
"module": "strings",
"section": "Core String Operations",
"summary": "Convert string to uppercase.",
"examples": [
"(string/upper \"hello\") ; => \"HELLO\""
],
"body": "Convert string to uppercase.\n\n```sema\n(string/upper \"hello\") ; => \"HELLO\"\n```"
},
{
"name": "string/words",
"module": "strings",
"section": "Case Conversion",
"summary": "Split a string into words on whitespace, underscores, and camelCase humps. Punctuation that isn't a separator stays attached to its word.",
"examples": [
"(string/words \"hello_world\") ; => (\"hello\" \"world\")\n(string/words \"helloWorld\") ; => (\"hello\" \"World\")\n(string/words \"Hello World!\") ; => (\"Hello\" \"World!\")"
],
"body": "Split a string into words on whitespace, underscores, and camelCase humps. Punctuation that\nisn't a separator stays attached to its word.\n\n```sema\n(string/words \"hello_world\") ; => (\"hello\" \"world\")\n(string/words \"helloWorld\") ; => (\"hello\" \"World\")\n(string/words \"Hello World!\") ; => (\"Hello\" \"World!\")\n```"
},
{
"name": "string/wrap",
"module": "strings",
"section": "Prefix & Suffix",
"summary": "Wrap a string with left and right delimiters.",
"examples": [
"(string/wrap \"hello\" \"(\" \")\") ; => \"(hello)\"\n(string/wrap \"hello\" \"**\") ; => \"**hello**\""
],
"body": "Wrap a string with left and right delimiters.\n\n```sema\n(string/wrap \"hello\" \"(\" \")\") ; => \"(hello)\"\n(string/wrap \"hello\" \"**\") ; => \"**hello**\"\n```"
},
{
"name": "symbol/to-string",
"aliases": [
"symbol->string"
],
"module": "strings",
"section": "Type Conversions",
"summary": "Convert a symbol to a string.",
"examples": [
"(symbol/to-string 'foo) ; => \"foo\""
],
"body": "Convert a symbol to a string.\n\n```sema\n(symbol/to-string 'foo) ; => \"foo\"\n```"
},
{
"name": "assert",
"module": "system",
"section": "Errors",
"summary": "Raise an error if `condition` is falsy, otherwise return `#t`. An optional second argument supplies the error message (default `\"assertion failed\"`).",
"params": [
{
"name": "condition",
"type": "any"
},
{
"name": "message",
"type": "string"
}
],
"examples": [
"(assert (> 2 1)) ; => #t\n(assert (= 1 2) \"must match\") ; raises \"must match\""
],
"body": "Raise an error if `condition` is falsy, otherwise return `#t`. An optional second argument supplies the error message (default `\"assertion failed\"`).\n\n```sema\n(assert (> 2 1)) ; => #t\n(assert (= 1 2) \"must match\") ; raises \"must match\"\n```"
},
{
"name": "assert=",
"module": "system",
"section": "Errors",
"summary": "Raise an error if `expected` and `actual` are not equal, with a message showing both values. Returns `#t` when they match.",
"params": [
{
"name": "expected",
"type": "any"
},
{
"name": "actual",
"type": "any"
}
],
"examples": [
"(assert= 4 (+ 2 2)) ; => #t\n(assert= 4 5) ; raises \"assertion failed: expected 4, got 5\""
],
"body": "Raise an error if `expected` and `actual` are not equal, with a message showing both values. Returns `#t` when they match.\n\n```sema\n(assert= 4 (+ 2 2)) ; => #t\n(assert= 4 5) ; raises \"assertion failed: expected 4, got 5\"\n```"
},
{
"name": "env",
"module": "system",
"section": "Environment Variables",
"summary": "Get the value of an environment variable. Returns `nil` if not set.",
"examples": [
"(env \"HOME\") ; => \"/Users/ada\"\n(env \"PATH\") ; => \"/usr/bin:/bin:...\"\n(env \"MISSING\") ; => nil"
],
"body": "Get the value of an environment variable. Returns `nil` if not set.\n\n```sema\n(env \"HOME\") ; => \"/Users/ada\"\n(env \"PATH\") ; => \"/usr/bin:/bin:...\"\n(env \"MISSING\") ; => nil\n```"
},
{
"name": "error",
"module": "system",
"section": "Errors",
"summary": "Raise an error (a catchable exception) with the given message. Non-string arguments are stringified.",
"params": [
{
"name": "message",
"type": "any"
}
],
"examples": [
"(try (error \"boom\") (catch e (get e :message))) ; => \"boom\""
],
"body": "Raise an error (a catchable exception) with the given message. Non-string arguments are stringified.\n\n```sema\n(try (error \"boom\") (catch e (get e :message))) ; => \"boom\"\n```"
},
{
"name": "exit",
"module": "system",
"section": "Shell & Process Control",
"summary": "Exit the process with a given status code.",
"examples": [
"(exit 0) ; exit successfully\n(exit 1) ; exit with error"
],
"body": "Exit the process with a given status code.\n\n```sema\n(exit 0) ; exit successfully\n(exit 1) ; exit with error\n```"
},
{
"name": "gensym",
"module": "system",
"section": "Metaprogramming",
"summary": "Generate a fresh, unique symbol. The optional `prefix` (default `\"g\"`) is prepended to a counter so each call yields a distinct name. Useful for hygienic macro expansion.",
"params": [
{
"name": "prefix",
"type": "string"
}
],
"returns": "symbol",
"examples": [
"(symbol? (gensym)) ; => #t\n(gensym \"tmp\") ; => tmp<n> (a unique symbol)"
],
"body": "Generate a fresh, unique symbol. The optional `prefix` (default `\"g\"`) is prepended to a counter so each call yields a distinct name. Useful for hygienic macro expansion.\n\n```sema\n(symbol? (gensym)) ; => #t\n(gensym \"tmp\") ; => tmp<n> (a unique symbol)\n```"
},
{
"name": "retry",
"module": "system",
"section": "Control",
"summary": "Call the zero-argument `thunk`, retrying with exponential backoff if it raises an error. Returns the first successful result, or re-raises the last error after all attempts fail. The optional `opts` map accepts `:max-attempts` (default 3), `:base-delay-ms` (default 100), and `:backoff` (default 2.0).",
"params": [
{
"name": "thunk",
"type": "function"
},
{
"name": "opts",
"type": "map"
}
],
"examples": [
"(retry (fn () (http/get \"https://example.com\")))\n(retry flaky-thunk {:max-attempts 5 :base-delay-ms 50})"
],
"body": "Call the zero-argument `thunk`, retrying with exponential backoff if it raises an error. Returns the first successful result, or re-raises the last error after all attempts fail. The optional `opts` map accepts `:max-attempts` (default 3), `:base-delay-ms` (default 100), and `:backoff` (default 2.0).\n\n```sema\n(retry (fn () (http/get \"https://example.com\")))\n(retry flaky-thunk {:max-attempts 5 :base-delay-ms 50})\n```"
},
{
"name": "shell",
"module": "system",
"section": "Shell & Process Control",
"summary": "Run a shell command and return its stdout as a string.",
"examples": [
"(shell \"ls -la\") ; => \"total 42\\n...\"\n(shell \"echo hello\") ; => \"hello\\n\""
],
"body": "Run a shell command and return its stdout as a string.\n\n```sema\n(shell \"ls -la\") ; => \"total 42\\n...\"\n(shell \"echo hello\") ; => \"hello\\n\"\n```"
},
{
"name": "spy",
"module": "system",
"section": "Debugging",
"summary": "Print `[label] value` to standard error and return `value` unchanged. Handy for inspecting intermediate values inside a pipeline.",
"params": [
{
"name": "label",
"type": "string"
},
{
"name": "value",
"type": "any"
}
],
"examples": [
"(spy \"x\" (+ 1 2)) ; prints \"[x] 3\" to stderr, returns 3"
],
"body": "Print `[label] value` to standard error and return `value` unchanged. Handy for inspecting intermediate values inside a pipeline.\n\n```sema\n(spy \"x\" (+ 1 2)) ; prints \"[x] 3\" to stderr, returns 3\n```"
},
{
"name": "sys/arch",
"module": "system",
"section": "System Information",
"summary": "Return the CPU architecture.",
"examples": [
"(sys/arch) ; => \"aarch64\" / \"x86_64\""
],
"body": "Return the CPU architecture.\n\n```sema\n(sys/arch) ; => \"aarch64\" / \"x86_64\"\n```"
},
{
"name": "sys/args",
"module": "system",
"section": "System Information",
"summary": "Return the command-line arguments as a list.",
"examples": [
"(sys/args) ; => (\"sema\" \"script.sema\" \"--flag\")"
],
"body": "Return the command-line arguments as a list.\n\n```sema\n(sys/args) ; => (\"sema\" \"script.sema\" \"--flag\")\n```"
},
{
"name": "sys/check-signals",
"module": "system",
"section": "Signals",
"summary": "Dispatch any pending signal callbacks. Call this from your event loop (typically right after `io/read-key` / `io/read-key-timeout` returns) so handlers run in a predictable place rather than asynchronously interrupting Sema code.",
"examples": [
"(let loop ()\n (sys/check-signals)\n (let ((key (io/read-key-timeout 50)))\n (when key (handle-key key))\n (loop)))"
],
"body": "Dispatch any pending signal callbacks. Call this from your event loop (typically right after `io/read-key` / `io/read-key-timeout` returns) so handlers run in a predictable place rather than asynchronously interrupting Sema code.\n\n```sema\n(let loop ()\n (sys/check-signals)\n (let ((key (io/read-key-timeout 50)))\n (when key (handle-key key))\n (loop)))\n```\n\nIf no signals are pending, this is essentially free — it just checks three atomic booleans."
},
{
"name": "sys/cwd",
"module": "system",
"section": "System Information",
"summary": "Return the current working directory.",
"examples": [
"(sys/cwd) ; => \"/current/dir\""
],
"body": "Return the current working directory.\n\n```sema\n(sys/cwd) ; => \"/current/dir\"\n```"
},
{
"name": "sys/elapsed",
"module": "system",
"section": "Process Information",
"summary": "Return nanoseconds elapsed since the process started.",
"examples": [
"(sys/elapsed) ; => 482937100"
],
"body": "Return nanoseconds elapsed since the process started.\n\n```sema\n(sys/elapsed) ; => 482937100\n```"
},
{
"name": "sys/env-all",
"module": "system",
"section": "Environment Variables",
"summary": "Return all environment variables as a map.",
"examples": [
"(sys/env-all) ; => {:HOME \"/Users/ada\" :PATH \"...\" ...}"
],
"body": "Return all environment variables as a map.\n\n```sema\n(sys/env-all) ; => {:HOME \"/Users/ada\" :PATH \"...\" ...}\n```"
},
{
"name": "sys/home-dir",
"module": "system",
"section": "Directory Paths",
"summary": "Return the user's home directory.",
"examples": [
"(sys/home-dir) ; => \"/Users/ada\""
],
"body": "Return the user's home directory.\n\n```sema\n(sys/home-dir) ; => \"/Users/ada\"\n```"
},
{
"name": "sys/hostname",
"module": "system",
"section": "Session Information",
"summary": "Return the system hostname.",
"examples": [
"(sys/hostname) ; => \"my-machine\""
],
"body": "Return the system hostname.\n\n```sema\n(sys/hostname) ; => \"my-machine\"\n```"
},
{
"name": "sys/interactive?",
"module": "system",
"section": "Session Information",
"summary": "Test if stdin is a TTY (i.e., running interactively).",
"examples": [
"(sys/interactive?) ; => #t in REPL, #f in scripts"
],
"body": "Test if stdin is a TTY (i.e., running interactively).\n\n```sema\n(sys/interactive?) ; => #t in REPL, #f in scripts\n```"
},
{
"name": "sys/interner-stats",
"module": "system",
"section": "System Information",
"summary": "Return statistics about the global symbol/string interner as a map with `:count` (number of interned strings) and `:bytes` (total bytes they occupy). Useful for diagnosing interner growth.",
"returns": "map",
"examples": [
"(sys/interner-stats) ; => {:count 1234 :bytes 56789}"
],
"body": "Return statistics about the global symbol/string interner as a map with `:count` (number of interned strings) and `:bytes` (total bytes they occupy). Useful for diagnosing interner growth.\n\n```sema\n(sys/interner-stats) ; => {:count 1234 :bytes 56789}\n```"
},
{
"name": "sys/on-signal",
"module": "system",
"section": "Signals",
"summary": "Register a callback for a signal. Multiple callbacks per signal are supported; they fire in registration order.",
"examples": [
"(sys/on-signal :int (fn ()\n (println \"interrupted, cleaning up\")\n (exit 0)))"
],
"body": "Register a callback for a signal. Multiple callbacks per signal are supported; they fire in registration order.\n\nSupported signals:\n\n| Keyword | Signal | Typical use |\n|----------|------------|--------------------------------------|\n| `:winch` | `SIGWINCH` | Terminal resize — redraw the UI |\n| `:int` | `SIGINT` | Ctrl-C — clean shutdown |\n| `:term` | `SIGTERM` | Termination request — clean shutdown |\n\n```sema\n(sys/on-signal :int (fn ()\n (println \"interrupted, cleaning up\")\n (exit 0)))\n```"
},
{
"name": "sys/os",
"module": "system",
"section": "System Information",
"summary": "Return the operating system name.",
"examples": [
"(sys/os) ; => \"macos\""
],
"body": "Return the operating system name.\n\n```sema\n(sys/os) ; => \"macos\"\n```"
},
{
"name": "sys/pid",
"module": "system",
"section": "Process Information",
"summary": "Return the current process ID.",
"examples": [
"(sys/pid) ; => 12345"
],
"body": "Return the current process ID.\n\n```sema\n(sys/pid) ; => 12345\n```"
},
{
"name": "sys/platform",
"module": "system",
"section": "System Information",
"summary": "Return the platform name.",
"examples": [
"(sys/platform) ; => \"macos\" / \"linux\" / \"windows\""
],
"body": "Return the platform name.\n\n```sema\n(sys/platform) ; => \"macos\" / \"linux\" / \"windows\"\n```"
},
{
"name": "sys/sema-home",
"module": "system",
"section": "System Information",
"summary": "Return the path to the Sema home directory — where Sema stores its configuration and runtime data.",
"returns": "string",
"examples": [
"(sys/sema-home) ; => \"/Users/you/.sema\""
],
"body": "Return the path to the Sema home directory — where Sema stores its configuration and runtime data.\n\n```sema\n(sys/sema-home) ; => \"/Users/you/.sema\"\n```"
},
{
"name": "sys/set-env",
"module": "system",
"section": "Environment Variables",
"summary": "Set an environment variable for the current process.",
"examples": [
"(sys/set-env \"KEY\" \"value\")\n(env \"KEY\") ; => \"value\""
],
"body": "Set an environment variable for the current process.\n\n```sema\n(sys/set-env \"KEY\" \"value\")\n(env \"KEY\") ; => \"value\"\n```"
},
{
"name": "sys/temp-dir",
"module": "system",
"section": "Directory Paths",
"summary": "Return the system temporary directory.",
"examples": [
"(sys/temp-dir) ; => \"/tmp\""
],
"body": "Return the system temporary directory.\n\n```sema\n(sys/temp-dir) ; => \"/tmp\"\n```"
},
{
"name": "sys/term-size",
"module": "system",
"section": "Terminal",
"summary": "Return the terminal's current size as a map `{:rows N :cols M}`, or `nil` when no controlling TTY is attached (e.g., when stdout is redirected to a file). Queries `ioctl(TIOCGWINSZ)` against stdout, then stderr, then stdin.",
"examples": [
"(sys/term-size)\n;; => {:rows 47 :cols 180}",
"(define (redraw size)\n ;; ... layout for size ...\n )\n\n(redraw (sys/term-size))\n(sys/on-signal :winch (fn () (redraw (sys/term-size))))"
],
"body": "Return the terminal's current size as a map `{:rows N :cols M}`, or `nil` when no controlling TTY is attached (e.g., when stdout is redirected to a file). Queries `ioctl(TIOCGWINSZ)` against stdout, then stderr, then stdin.\n\n```sema\n(sys/term-size)\n;; => {:rows 47 :cols 180}\n```\n\nPair with `sys/on-signal :winch` to redraw on terminal resize:\n\n```sema\n(define (redraw size)\n ;; ... layout for size ...\n )\n\n(redraw (sys/term-size))\n(sys/on-signal :winch (fn () (redraw (sys/term-size))))\n```\n\nReturns `nil` on Windows and any non-Unix target."
},
{
"name": "sys/tty",
"module": "system",
"section": "Process Information",
"summary": "Return the TTY device path, or `nil` if not running in a terminal.",
"examples": [
"(sys/tty) ; => \"/dev/ttys003\" or nil"
],
"body": "Return the TTY device path, or `nil` if not running in a terminal.\n\n```sema\n(sys/tty) ; => \"/dev/ttys003\" or nil\n```"
},
{
"name": "sys/user",
"module": "system",
"section": "Session Information",
"summary": "Return the current username.",
"examples": [
"(sys/user) ; => \"ada\""
],
"body": "Return the current username.\n\n```sema\n(sys/user) ; => \"ada\"\n```"
},
{
"name": "sys/which",
"module": "system",
"section": "Process Information",
"summary": "Find the full path to an executable, or `nil` if not found.",
"examples": [
"(sys/which \"cargo\") ; => \"/Users/ada/.cargo/bin/cargo\"\n(sys/which \"nonexistent\") ; => nil"
],
"body": "Find the full path to an executable, or `nil` if not found.\n\n```sema\n(sys/which \"cargo\") ; => \"/Users/ada/.cargo/bin/cargo\"\n(sys/which \"nonexistent\") ; => nil\n```"
},
{
"name": "time",
"module": "system",
"section": "Timing",
"summary": "Call the zero-argument `thunk`, print the elapsed wall-clock time (in milliseconds) to standard error, and return the thunk's result. See `time/ms` to capture the duration as a value instead.",
"params": [
{
"name": "thunk",
"type": "function"
}
],
"examples": [
"(time (fn () (+ 1 2))) ; prints \"Elapsed: 0.003ms\", returns 3"
],
"body": "Call the zero-argument `thunk`, print the elapsed wall-clock time (in milliseconds) to standard error, and return the thunk's result. See `time/ms` to capture the duration as a value instead.\n\n```sema\n(time (fn () (+ 1 2))) ; prints \"Elapsed: 0.003ms\", returns 3\n```"
},
{
"name": "io/read-key",
"module": "terminal",
"section": "Raw-Mode Input",
"summary": "Block until a single keypress arrives, then return a map describing it. Returns `nil` on EOF (after which `io/eof?` returns `#t`).",
"examples": [
"(io/read-key)\n;; => {:kind :char :char \"a\"}"
],
"body": "Block until a single keypress arrives, then return a map describing it. Returns `nil` on EOF (after which `io/eof?` returns `#t`).\n\n```sema\n(io/read-key)\n;; => {:kind :char :char \"a\"}\n```\n\nThe map's `:kind` field is one of:\n\n| `:kind` | Other keys | Meaning |\n|-----------|-------------------------|-------------------------------------------------|\n| `:char` | `:char` (string) | A printable character (UTF-8 multi-byte handled) |\n| `:ctrl` | `:char` (string) | Ctrl + letter (e.g., Ctrl-C → `{:kind :ctrl :char \"c\"}`) |\n| `:alt` | `:char` (string) | Alt/Meta + character (ESC + char sequence) |\n| `:key` | `:name` (keyword) | Named key — see table below |\n\nNamed keys (`:kind :key`) currently emitted:\n\n`:enter` `:tab` `:backspace` `:esc` `:up` `:down` `:left` `:right` `:home` `:end` `:delete` `:page-up` `:page-down` `:f1` `:f2` `:f3` `:f4`\n\nCSI/SS3 escape sequences (arrow keys, F1–F4, Page Up/Down, Delete) and UTF-8 continuation bytes are decoded for you with a 20 ms continuation-byte window. F5–F12 and Insert use longer escape sequences that aren't decoded yet — they fall through as raw characters."
},
{
"name": "io/read-key-timeout",
"module": "terminal",
"section": "Raw-Mode Input",
"summary": "Like `io/read-key`, but returns `nil` after `timeout-ms` milliseconds with no input. Backed by `select(2)`, so it doesn't burn CPU.",
"examples": [
"(io/read-key-timeout 100) ; => key map, or nil after 100ms",
"(let loop ()\n (sys/check-signals)\n (let ((key (io/read-key-timeout 50)))\n (when key (handle-key key))\n (loop)))"
],
"body": "Like `io/read-key`, but returns `nil` after `timeout-ms` milliseconds with no input. Backed by `select(2)`, so it doesn't burn CPU.\n\n```sema\n(io/read-key-timeout 100) ; => key map, or nil after 100ms\n```\n\nUse this to drive an animation loop or to poll signals between renders:\n\n```sema\n(let loop ()\n (sys/check-signals)\n (let ((key (io/read-key-timeout 50)))\n (when key (handle-key key))\n (loop)))\n```"
},
{
"name": "io/tty-raw!",
"module": "terminal",
"section": "Raw-Mode Input",
"summary": "Put stdin into raw mode. Returns an **integer restore-token** on success, or `nil` if stdin is not a TTY (e.g., when input is piped from a file). Always pair with `io/tty-restore!` so the user's shell isn't left in raw mode if your program crashes.",
"examples": [
"(define tok (io/tty-raw!))\n(when tok\n ;; ... read keys, draw UI ...\n (io/tty-restore! tok))"
],
"body": "Put stdin into raw mode. Returns an **integer restore-token** on success, or `nil` if stdin is not a TTY (e.g., when input is piped from a file). Always pair with `io/tty-restore!` so the user's shell isn't left in raw mode if your program crashes.\n\n```sema\n(define tok (io/tty-raw!))\n(when tok\n ;; ... read keys, draw UI ...\n (io/tty-restore! tok))\n```"
},
{
"name": "io/tty-restore!",
"module": "terminal",
"section": "Raw-Mode Input",
"summary": "Restore the TTY to cooked mode using the token returned by `io/tty-raw!`.",
"examples": [
"(io/tty-restore! tok)"
],
"body": "Restore the TTY to cooked mode using the token returned by `io/tty-raw!`.\n\n```sema\n(io/tty-restore! tok)\n```"
},
{
"name": "term/black",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in black in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/black \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in black in a terminal that supports color.\n\n```sema\n(term/black \"hello\")\n```"
},
{
"name": "term/blue",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in blue in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/blue \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in blue in a terminal that supports color.\n\n```sema\n(term/blue \"hello\")\n```"
},
{
"name": "term/bold",
"module": "terminal",
"section": "Modifiers",
"summary": "Render text in **bold** (increased intensity).",
"examples": [
"(term/bold \"important\")\n(println (term/bold \"Warning: check your input\"))"
],
"body": "Render text in **bold** (increased intensity).\n\n```sema\n(term/bold \"important\")\n(println (term/bold \"Warning: check your input\"))\n```"
},
{
"name": "term/cyan",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in cyan in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/cyan \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in cyan in a terminal that supports color.\n\n```sema\n(term/cyan \"hello\")\n```"
},
{
"name": "term/dim",
"module": "terminal",
"section": "Modifiers",
"summary": "Render text with decreased intensity.",
"examples": [
"(term/dim \"less important\")"
],
"body": "Render text with decreased intensity.\n\n```sema\n(term/dim \"less important\")\n```"
},
{
"name": "term/gray",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in gray in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/gray \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in gray in a terminal that supports color.\n\n```sema\n(term/gray \"hello\")\n```"
},
{
"name": "term/green",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in green in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/green \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in green in a terminal that supports color.\n\n```sema\n(term/green \"hello\")\n```"
},
{
"name": "term/inverse",
"module": "terminal",
"section": "Modifiers",
"summary": "Swap foreground and background colors.",
"examples": [
"(term/inverse \"highlighted\")"
],
"body": "Swap foreground and background colors.\n\n```sema\n(term/inverse \"highlighted\")\n```"
},
{
"name": "term/italic",
"module": "terminal",
"section": "Modifiers",
"summary": "Render text in *italic*.",
"examples": [
"(term/italic \"emphasis\")"
],
"body": "Render text in *italic*.\n\n```sema\n(term/italic \"emphasis\")\n```"
},
{
"name": "term/magenta",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in magenta in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/magenta \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in magenta in a terminal that supports color.\n\n```sema\n(term/magenta \"hello\")\n```"
},
{
"name": "term/red",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in red in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/red \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in red in a terminal that supports color.\n\n```sema\n(term/red \"hello\")\n```"
},
{
"name": "term/rgb",
"module": "terminal",
"section": "True Color",
"summary": "Apply 24-bit true color to text. Takes the text followed by red, green, and blue values (integers 0–255).",
"examples": [
"(term/rgb \"orange\" 255 165 0)\n(term/rgb \"coral\" 255 127 80)\n(term/rgb \"teal\" 0 128 128)\n(term/rgb \"hot pink\" 255 105 180)",
";; Build a gradient\n(for-each\n (lambda (i)\n (display (term/rgb \"█\" (* i 25) 50 (- 255 (* i 25)))))\n (range 11))\n(println)"
],
"body": "Apply 24-bit true color to text. Takes the text followed by red, green, and blue values (integers 0–255).\n\n```sema\n(term/rgb \"orange\" 255 165 0)\n(term/rgb \"coral\" 255 127 80)\n(term/rgb \"teal\" 0 128 128)\n(term/rgb \"hot pink\" 255 105 180)\n```\n\nUses the `ESC[38;2;r;g;bm` escape sequence format, which is supported by most modern terminals.\n\n```sema\n;; Build a gradient\n(for-each\n (lambda (i)\n (display (term/rgb \"█\" (* i 25) 50 (- 255 (* i 25)))))\n (range 11))\n(println)\n```"
},
{
"name": "term/spinner-start",
"module": "terminal",
"section": "Spinners",
"summary": "Start a spinner with a message. Returns an integer spinner ID used to update or stop it.",
"examples": [
"(define id (term/spinner-start \"Loading data...\"))"
],
"body": "Start a spinner with a message. Returns an integer spinner ID used to update or stop it.\n\n```sema\n(define id (term/spinner-start \"Loading data...\"))\n```"
},
{
"name": "term/spinner-stop",
"module": "terminal",
"section": "Spinners",
"summary": "Stop a running spinner and optionally display a final status line. The spinner line is cleared from the terminal before the final status is printed.",
"examples": [
"(term/spinner-stop id)",
"(term/spinner-stop id {:symbol \"✔\" :text \"Done\"})"
],
"body": "Stop a running spinner and optionally display a final status line. The spinner line is cleared from the terminal before the final status is printed.\n\n**Without options** — just clears the spinner:\n\n```sema\n(term/spinner-stop id)\n```\n\n**With options map** — displays a final symbol and text:\n\n```sema\n(term/spinner-stop id {:symbol \"✔\" :text \"Done\"})\n```\n\nThe options map supports two keys:\n\n| Key | Type | Description |\n|-----------|--------|--------------------------------------|\n| `:symbol` | string | Symbol to display (e.g., `\"✔\"`, `\"✗\"`, `\"⚠\"`) |\n| `:text` | string | Final status message |\n\nBoth keys are optional. The final line is printed to stderr as `symbol text`."
},
{
"name": "term/spinner-update",
"module": "terminal",
"section": "Spinners",
"summary": "Update the message displayed next to a running spinner.",
"examples": [
"(term/spinner-update id \"Processing records...\")\n(term/spinner-update id \"Almost done...\")"
],
"body": "Update the message displayed next to a running spinner.\n\n```sema\n(term/spinner-update id \"Processing records...\")\n(term/spinner-update id \"Almost done...\")\n```"
},
{
"name": "term/strikethrough",
"module": "terminal",
"section": "Modifiers",
"summary": "Render text with a ~~strikethrough~~.",
"examples": [
"(term/strikethrough \"deprecated\")"
],
"body": "Render text with a ~~strikethrough~~.\n\n```sema\n(term/strikethrough \"deprecated\")\n```"
},
{
"name": "term/strip",
"module": "terminal",
"section": "Stripping ANSI Codes",
"summary": "Remove all ANSI escape sequences from a string, returning plain text.",
"examples": [
"(term/strip (term/bold \"hello\")) ; => \"hello\"\n(term/strip (term/style \"hi\" :red :bold)) ; => \"hi\"\n(term/strip (term/rgb \"color\" 255 0 0)) ; => \"color\"\n(term/strip \"no codes here\") ; => \"no codes here\"",
";; Write clean text to a file, styled text to terminal\n(define msg (term/green \"Build succeeded\"))\n(println msg) ; styled on terminal\n(file/write \"build.log\" (term/strip msg)) ; clean in log file"
],
"body": "Remove all ANSI escape sequences from a string, returning plain text.\n\n```sema\n(term/strip (term/bold \"hello\")) ; => \"hello\"\n(term/strip (term/style \"hi\" :red :bold)) ; => \"hi\"\n(term/strip (term/rgb \"color\" 255 0 0)) ; => \"color\"\n(term/strip \"no codes here\") ; => \"no codes here\"\n```\n\nThis is useful when you need plain text for logging to files, comparisons, or passing to functions that don't understand ANSI codes:\n\n```sema\n;; Write clean text to a file, styled text to terminal\n(define msg (term/green \"Build succeeded\"))\n(println msg) ; styled on terminal\n(file/write \"build.log\" (term/strip msg)) ; clean in log file\n```"
},
{
"name": "term/style",
"module": "terminal",
"section": "Combined Styles",
"summary": "Apply multiple styles at once using keywords. The first argument is the text, followed by one or more style keywords.",
"examples": [
"(term/style \"danger\" :bold :red)\n(term/style \"notice\" :italic :yellow :underline)\n(term/style \"subtle\" :dim :gray)",
"(term/style \"plain text\") ; => \"plain text\" (no ANSI codes)",
"(term/style \"text\" :blink) ; Error: unknown style keyword :blink"
],
"body": "Apply multiple styles at once using keywords. The first argument is the text, followed by one or more style keywords.\n\n```sema\n(term/style \"danger\" :bold :red)\n(term/style \"notice\" :italic :yellow :underline)\n(term/style \"subtle\" :dim :gray)\n```\n\nInternally, `term/style` combines ANSI codes with `;` separators into a single escape sequence (e.g., `ESC[1;31m` for bold red), which is more efficient than nesting individual style functions.\n\nIf called with no style keywords, the text is returned unstyled.\n\n```sema\n(term/style \"plain text\") ; => \"plain text\" (no ANSI codes)\n```\n\nAn unknown keyword produces an error:\n\n```sema\n(term/style \"text\" :blink) ; Error: unknown style keyword :blink\n```\n\n#### Style keyword reference\n\n| Keyword | Effect | ANSI Code |\n|------------------|----------------|-----------|\n| `:bold` | Bold | 1 |\n| `:dim` | Dim | 2 |\n| `:italic` | Italic | 3 |\n| `:underline` | Underline | 4 |\n| `:inverse` | Inverse | 7 |\n| `:strikethrough` | Strikethrough | 9 |\n| `:black` | Black text | 30 |\n| `:red` | Red text | 31 |\n| `:green` | Green text | 32 |\n| `:yellow` | Yellow text | 33 |\n| `:blue` | Blue text | 34 |\n| `:magenta` | Magenta text | 35 |\n| `:cyan` | Cyan text | 36 |\n| `:white` | White text | 37 |\n| `:gray` | Gray text | 90 |"
},
{
"name": "term/underline",
"module": "terminal",
"section": "Modifiers",
"summary": "Render text with an underline.",
"examples": [
"(term/underline \"click here\")"
],
"body": "Render text with an underline.\n\n```sema\n(term/underline \"click here\")\n```"
},
{
"name": "term/white",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in white in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/white \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in white in a terminal that supports color.\n\n```sema\n(term/white \"hello\")\n```"
},
{
"name": "term/yellow",
"module": "terminal",
"section": "Colors",
"summary": "Wrap `text` in ANSI escape codes so it renders in yellow in a terminal that supports color.",
"params": [
{
"name": "text",
"type": "string"
}
],
"returns": "string",
"examples": [
"(term/yellow \"hello\")"
],
"body": "Wrap `text` in ANSI escape codes so it renders in yellow in a terminal that supports color.\n\n```sema\n(term/yellow \"hello\")\n```"
},
{
"name": "document/chunk",
"module": "text-processing",
"section": "Documents",
"summary": "Chunk a document, preserving and extending metadata. Each chunk gets `:chunk-index` and `:total-chunks` added to its metadata.",
"examples": [
"(document/chunk\n (document/create \"long text...\" {:source \"paper.pdf\"})\n {:size 500})\n; => ({:text \"chunk 1...\" :metadata {:source \"paper.pdf\" :chunk-index 0 :total-chunks 3}}\n; {:text \"chunk 2...\" :metadata {:source \"paper.pdf\" :chunk-index 1 :total-chunks 3}}\n; ...)"
],
"body": "Chunk a document, preserving and extending metadata. Each chunk gets `:chunk-index` and `:total-chunks` added to its metadata.\n\n```sema\n(document/chunk\n (document/create \"long text...\" {:source \"paper.pdf\"})\n {:size 500})\n; => ({:text \"chunk 1...\" :metadata {:source \"paper.pdf\" :chunk-index 0 :total-chunks 3}}\n; {:text \"chunk 2...\" :metadata {:source \"paper.pdf\" :chunk-index 1 :total-chunks 3}}\n; ...)\n```"
},
{
"name": "document/create",
"module": "text-processing",
"section": "Documents",
"summary": "Create a document map with `:text` and `:metadata`.",
"examples": [
"(document/create \"Hello world\" {:source \"test.txt\" :page 1})\n; => {:metadata {:page 1 :source \"test.txt\"} :text \"Hello world\"}"
],
"body": "Create a document map with `:text` and `:metadata`.\n\n```sema\n(document/create \"Hello world\" {:source \"test.txt\" :page 1})\n; => {:metadata {:page 1 :source \"test.txt\"} :text \"Hello world\"}\n```"
},
{
"name": "document/metadata",
"module": "text-processing",
"section": "Documents",
"summary": "Extract the metadata from a document.",
"examples": [
"(document/metadata doc) ; => {:source \"test.txt\" :page 1}"
],
"body": "Extract the metadata from a document.\n\n```sema\n(document/metadata doc) ; => {:source \"test.txt\" :page 1}\n```"
},
{
"name": "document/text",
"module": "text-processing",
"section": "Documents",
"summary": "Extract the text from a document.",
"examples": [
"(document/text doc) ; => \"Hello world\""
],
"body": "Extract the text from a document.\n\n```sema\n(document/text doc) ; => \"Hello world\"\n```"
},
{
"name": "prompt/render",
"module": "text-processing",
"section": "Prompt Templates",
"summary": "Render a template by substituting `{{key}}` placeholders with values from a map. Missing keys are left as-is.",
"examples": [
"(prompt/render \"Hello {{name}}, welcome to {{place}}.\"\n {:name \"Alice\" :place \"Wonderland\"})\n; => \"Hello Alice, welcome to Wonderland.\"\n\n(prompt/render \"Hello {{name}}, {{missing}}.\" {:name \"Bob\"})\n; => \"Hello Bob, {{missing}}.\"\n\n;; Non-string values are stringified\n(prompt/render \"Count: {{n}}\" {:n 42})\n; => \"Count: 42\""
],
"body": "Render a template by substituting `{{key}}` placeholders with values from a map. Missing keys are left as-is.\n\n```sema\n(prompt/render \"Hello {{name}}, welcome to {{place}}.\"\n {:name \"Alice\" :place \"Wonderland\"})\n; => \"Hello Alice, welcome to Wonderland.\"\n\n(prompt/render \"Hello {{name}}, {{missing}}.\" {:name \"Bob\"})\n; => \"Hello Bob, {{missing}}.\"\n\n;; Non-string values are stringified\n(prompt/render \"Count: {{n}}\" {:n 42})\n; => \"Count: 42\"\n```"
},
{
"name": "prompt/template",
"module": "text-processing",
"section": "Prompt Templates",
"summary": "Create a template string for use with `prompt/render`.",
"examples": [
"(define tmpl (prompt/template \"Hello {{name}}, welcome to {{place}}.\"))"
],
"body": "Create a template string for use with `prompt/render`.\n\n```sema\n(define tmpl (prompt/template \"Hello {{name}}, welcome to {{place}}.\"))\n```"
},
{
"name": "text/chunk",
"module": "text-processing",
"section": "Text Chunking",
"summary": "Recursively split text into chunks, trying natural boundaries (paragraphs, sentences, words) before hard-splitting. Takes text and an optional options map.",
"examples": [
"(text/chunk \"Long text here...\")\n(text/chunk \"Long text here...\" {:size 500 :overlap 100})"
],
"body": "Recursively split text into chunks, trying natural boundaries (paragraphs, sentences, words) before hard-splitting. Takes text and an optional options map.\n\n```sema\n(text/chunk \"Long text here...\")\n(text/chunk \"Long text here...\" {:size 500 :overlap 100})\n```\n\nOptions: `:size` (default 1000), `:overlap` (default 200). Returns a list of strings."
},
{
"name": "text/chunk-by-separator",
"module": "text-processing",
"section": "Text Chunking",
"summary": "Split text by a specific separator string.",
"examples": [
"(text/chunk-by-separator \"a\\nb\\nc\" \"\\n\") ; => (\"a\" \"b\" \"c\")"
],
"body": "Split text by a specific separator string.\n\n```sema\n(text/chunk-by-separator \"a\\nb\\nc\" \"\\n\") ; => (\"a\" \"b\" \"c\")\n```"
},
{
"name": "text/clean-whitespace",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Collapse multiple whitespace characters (spaces, newlines, tabs) into single spaces.",
"examples": [
"(text/clean-whitespace \" hello world \\n\\n foo \")\n; => \"hello world foo\""
],
"body": "Collapse multiple whitespace characters (spaces, newlines, tabs) into single spaces.\n\n```sema\n(text/clean-whitespace \" hello world \\n\\n foo \")\n; => \"hello world foo\"\n```"
},
{
"name": "text/excerpt",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Extract a snippet around a search term with omission markers. Case-insensitive search. Returns `nil` if query not found.",
"examples": [
"(text/excerpt \"The quick brown fox jumps over the lazy dog\" \"fox\" {:radius 10})\n; => \"...brown fox jumps ov...\"\n\n(text/excerpt \"Hello world\" \"Hello\")\n; => \"Hello world\"\n\n;; Custom omission marker\n(text/excerpt \"Long text here...\" \"text\" {:radius 5 :omission \"[…]\"})\n; => \"[…]g text here[…]\""
],
"body": "Extract a snippet around a search term with omission markers. Case-insensitive search. Returns `nil` if query not found.\n\n```sema\n(text/excerpt \"The quick brown fox jumps over the lazy dog\" \"fox\" {:radius 10})\n; => \"...brown fox jumps ov...\"\n\n(text/excerpt \"Hello world\" \"Hello\")\n; => \"Hello world\"\n\n;; Custom omission marker\n(text/excerpt \"Long text here...\" \"text\" {:radius 5 :omission \"[…]\"})\n; => \"[…]g text here[…]\"\n```\n\nOptions map (optional third argument):\n\n- `:radius` — number of characters to show on each side (default: 100)\n- `:omission` — marker string for truncated parts (default: `\"...\"`)"
},
{
"name": "text/normalize-newlines",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Convert `\\r\\n` (Windows) and `\\r` (old Mac) line endings to `\\n` (Unix).",
"examples": [
"(text/normalize-newlines \"line1\\r\\nline2\\rline3\")\n; => \"line1\\nline2\\nline3\""
],
"body": "Convert `\\r\\n` (Windows) and `\\r` (old Mac) line endings to `\\n` (Unix).\n\n```sema\n(text/normalize-newlines \"line1\\r\\nline2\\rline3\")\n; => \"line1\\nline2\\nline3\"\n```"
},
{
"name": "text/split-sentences",
"module": "text-processing",
"section": "Text Chunking",
"summary": "Split text into sentences at `.`, `!`, `?` boundaries.",
"examples": [
"(text/split-sentences \"Hello world. How are you? Fine.\")\n; => (\"Hello world.\" \"How are you?\" \"Fine.\")"
],
"body": "Split text into sentences at `.`, `!`, `?` boundaries.\n\n```sema\n(text/split-sentences \"Hello world. How are you? Fine.\")\n; => (\"Hello world.\" \"How are you?\" \"Fine.\")\n```"
},
{
"name": "text/strip-html",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Remove HTML tags and decode common entities (`&`, `<`, `>`, `"`, `'`, `'`, ` `).",
"examples": [
"(text/strip-html \"<p>Hello <b>world</b></p>\") ; => \"Hello world\"\n(text/strip-html \"a & b < c\") ; => \"a & b < c\""
],
"body": "Remove HTML tags and decode common entities (`&`, `<`, `>`, `"`, `'`, `'`, ` `).\n\n```sema\n(text/strip-html \"<p>Hello <b>world</b></p>\") ; => \"Hello world\"\n(text/strip-html \"a & b < c\") ; => \"a & b < c\"\n```"
},
{
"name": "text/trim-indent",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Remove common leading indentation from all lines.",
"examples": [
"(text/trim-indent \" hello\\n world\") ; => \"hello\\nworld\"\n(text/trim-indent \" hello\\n world\") ; => \"hello\\n world\""
],
"body": "Remove common leading indentation from all lines.\n\n```sema\n(text/trim-indent \" hello\\n world\") ; => \"hello\\nworld\"\n(text/trim-indent \" hello\\n world\") ; => \"hello\\n world\"\n```"
},
{
"name": "text/truncate",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Truncate text to a maximum length with a suffix. Takes text, max-length, and optional suffix (default `\"...\"`).",
"examples": [
"(text/truncate \"hello world\" 5) ; => \"he...\"\n(text/truncate \"hello world\" 8 \"…\") ; => \"hello w…\"\n(text/truncate \"hi\" 10) ; => \"hi\""
],
"body": "Truncate text to a maximum length with a suffix. Takes text, max-length, and optional suffix (default `\"...\"`).\n\n```sema\n(text/truncate \"hello world\" 5) ; => \"he...\"\n(text/truncate \"hello world\" 8 \"…\") ; => \"hello w…\"\n(text/truncate \"hi\" 10) ; => \"hi\"\n```"
},
{
"name": "text/word-count",
"module": "text-processing",
"section": "Text Cleaning",
"summary": "Count words in text (split by whitespace).",
"examples": [
"(text/word-count \"hello world foo bar\") ; => 4"
],
"body": "Count words in text (split by whitespace).\n\n```sema\n(text/word-count \"hello world foo bar\") ; => 4\n```"
},
{
"name": "time/ms",
"module": "time",
"summary": "Call a zero-argument thunk and return how long it took to run, in milliseconds (as a float). Useful for quick timing.",
"params": [
{
"name": "thunk",
"type": "function"
}
],
"returns": "float",
"examples": [
"(time/ms (lambda () (sum (range 1000)))) ; => 0.042"
],
"body": "Call a zero-argument thunk and return how long it took to run, in milliseconds (as a float). Useful for quick timing.\n\n```sema\n(time/ms (lambda () (sum (range 1000)))) ; => 0.042\n```"
},
{
"name": "toml/decode",
"module": "toml",
"summary": "Parse a TOML document string into a map. Table keys become keywords; nested tables become nested maps and arrays become lists.",
"params": [
{
"name": "s",
"type": "string"
}
],
"returns": "map",
"examples": [
"(toml/decode \"name = \\\"sema\\\"\\nversion = 2\")\n; => {:name \"sema\" :version 2}"
],
"body": "Parse a TOML document string into a map. Table keys become keywords; nested tables become nested maps and arrays become lists.\n\n```sema\n(toml/decode \"name = \\\"sema\\\"\\nversion = 2\")\n; => {:name \"sema\" :version 2}\n```"
},
{
"name": "toml/encode",
"module": "toml",
"summary": "Serialize a map to a TOML document string. The top-level value must be a map; keys are emitted as TOML keys and values are converted to their TOML equivalents.",
"params": [
{
"name": "m",
"type": "map"
}
],
"returns": "string",
"examples": [
"(toml/encode {:name \"sema\" :version 2})\n; => \"name = \\\"sema\\\"\\nversion = 2\\n\""
],
"body": "Serialize a map to a TOML document string. The top-level value must be a map; keys are emitted as TOML keys and values are converted to their TOML equivalents.\n\n```sema\n(toml/encode {:name \"sema\" :version 2})\n; => \"name = \\\"sema\\\"\\nversion = 2\\n\"\n```"
},
{
"name": "tool/description",
"module": "tool",
"summary": "Return the description of a tool definition.",
"params": [
{
"name": "tool",
"type": "tool"
}
],
"returns": "string",
"examples": [
"(tool/description get-weather) ; => \"Fetch the current weather for a city\""
],
"body": "Return the description of a tool definition.\n\n```sema\n(tool/description get-weather) ; => \"Fetch the current weather for a city\"\n```"
},
{
"name": "tool/name",
"module": "tool",
"summary": "Return the name of a tool definition.",
"params": [
{
"name": "tool",
"type": "tool"
}
],
"returns": "string",
"examples": [
"(tool/name get-weather) ; => \"get-weather\""
],
"body": "Return the name of a tool definition.\n\n```sema\n(tool/name get-weather) ; => \"get-weather\"\n```"
},
{
"name": "tool/parameters",
"module": "tool",
"summary": "Return the parameter schema of a tool definition (the map describing the tool's accepted arguments).",
"params": [
{
"name": "tool",
"type": "tool"
}
],
"returns": "map",
"examples": [
"(tool/parameters get-weather)"
],
"body": "Return the parameter schema of a tool definition (the map describing the tool's accepted arguments).\n\n```sema\n(tool/parameters get-weather)\n```"
},
{
"name": "f64-array",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an f64 array from values.",
"examples": [
"(f64-array 1.0 2.5 3.7) ; => #f64(1 2.5 3.7)\n(f64-array) ; => #f64()"
],
"body": "Create an f64 array from values.\n\n```sema\n(f64-array 1.0 2.5 3.7) ; => #f64(1 2.5 3.7)\n(f64-array) ; => #f64()\n```"
},
{
"name": "f64-array/dot",
"module": "typed-arrays",
"section": "Aggregation",
"summary": "Compute the dot product of two f64 arrays (must be the same length).",
"examples": [
"(f64-array/dot (f64-array 1.0 2.0 3.0) (f64-array 4.0 5.0 6.0))\n; => 32.0 (1*4 + 2*5 + 3*6)"
],
"body": "Compute the dot product of two f64 arrays (must be the same length).\n\n```sema\n(f64-array/dot (f64-array 1.0 2.0 3.0) (f64-array 4.0 5.0 6.0))\n; => 32.0 (1*4 + 2*5 + 3*6)\n```"
},
{
"name": "f64-array/fold",
"aliases": [
"i64-array/fold"
],
"module": "typed-arrays",
"section": "Higher-Order Functions",
"summary": "Fold over a typed array with an accumulator.",
"examples": [
"(f64-array/fold (lambda (acc x) (+ acc x)) 0.0 (f64-array 1.0 2.0 3.0))\n; => 6.0\n\n(i64-array/fold (lambda (acc x) (max acc x)) 0 (i64-array 3 1 4 1 5))\n; => 5"
],
"body": "Fold over a typed array with an accumulator.\n\n```sema\n(f64-array/fold (lambda (acc x) (+ acc x)) 0.0 (f64-array 1.0 2.0 3.0))\n; => 6.0\n\n(i64-array/fold (lambda (acc x) (max acc x)) 0 (i64-array 3 1 4 1 5))\n; => 5\n```"
},
{
"name": "f64-array/from-list",
"module": "typed-arrays",
"section": "Construction",
"summary": "Convert a list of numbers to an f64 array.",
"examples": [
"(f64-array/from-list '(1 2 3)) ; => #f64(1 2 3)"
],
"body": "Convert a list of numbers to an f64 array.\n\n```sema\n(f64-array/from-list '(1 2 3)) ; => #f64(1 2 3)\n```"
},
{
"name": "f64-array/length",
"aliases": [
"i64-array/length"
],
"module": "typed-arrays",
"section": "Access & Mutation",
"summary": "Return the number of elements.",
"examples": [
"(f64-array/length (f64-array 1.0 2.0 3.0)) ; => 3\n(i64-array/length (i64-array/make 10)) ; => 10"
],
"body": "Return the number of elements.\n\n```sema\n(f64-array/length (f64-array 1.0 2.0 3.0)) ; => 3\n(i64-array/length (i64-array/make 10)) ; => 10\n```"
},
{
"name": "f64-array/make",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an f64 array of a given length, optionally filled with a value (default `0.0`).",
"examples": [
"(f64-array/make 5) ; => #f64(0 0 0 0 0)\n(f64-array/make 3 1.5) ; => #f64(1.5 1.5 1.5)"
],
"body": "Create an f64 array of a given length, optionally filled with a value (default `0.0`).\n\n```sema\n(f64-array/make 5) ; => #f64(0 0 0 0 0)\n(f64-array/make 3 1.5) ; => #f64(1.5 1.5 1.5)\n```"
},
{
"name": "f64-array/map",
"aliases": [
"i64-array/map"
],
"module": "typed-arrays",
"section": "Higher-Order Functions",
"summary": "Apply a function to each element, returning a new typed array. The callback must return the matching numeric type.",
"examples": [
"(f64-array/map (lambda (x) (* x 2.0)) (f64-array 1.0 2.0 3.0))\n; => #f64(2 4 6)\n\n(i64-array/map (lambda (x) (* x x)) (i64-array 1 2 3 4))\n; => #i64(1 4 9 16)"
],
"body": "Apply a function to each element, returning a new typed array. The callback must return the matching numeric type.\n\n```sema\n(f64-array/map (lambda (x) (* x 2.0)) (f64-array 1.0 2.0 3.0))\n; => #f64(2 4 6)\n\n(i64-array/map (lambda (x) (* x x)) (i64-array 1 2 3 4))\n; => #i64(1 4 9 16)\n```"
},
{
"name": "f64-array/range",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an f64 array from a numeric range. `(f64-array/range start end)` or `(f64-array/range start end step)`.",
"examples": [
"(f64-array/range 0 5) ; => #f64(0 1 2 3 4)\n(f64-array/range 0 1 0.25) ; => #f64(0 0.25 0.5 0.75)"
],
"body": "Create an f64 array from a numeric range. `(f64-array/range start end)` or `(f64-array/range start end step)`.\n\n```sema\n(f64-array/range 0 5) ; => #f64(0 1 2 3 4)\n(f64-array/range 0 1 0.25) ; => #f64(0 0.25 0.5 0.75)\n```"
},
{
"name": "f64-array/ref",
"aliases": [
"i64-array/ref"
],
"module": "typed-arrays",
"section": "Access & Mutation",
"summary": "Get the element at a given index.",
"examples": [
"(f64-array/ref (f64-array 1.0 2.0 3.0) 1) ; => 2.0\n(i64-array/ref (i64-array 10 20 30) 0) ; => 10"
],
"body": "Get the element at a given index.\n\n```sema\n(f64-array/ref (f64-array 1.0 2.0 3.0) 1) ; => 2.0\n(i64-array/ref (i64-array 10 20 30) 0) ; => 10\n```"
},
{
"name": "f64-array/set!",
"aliases": [
"i64-array/set!"
],
"module": "typed-arrays",
"section": "Access & Mutation",
"summary": "Set the element at a given index. Uses copy-on-write -- the original array is unchanged unless it has a single reference.",
"examples": [
"(f64-array/set! (f64-array 1.0 2.0 3.0) 1 9.9) ; => #f64(1 9.9 3)\n(i64-array/set! (i64-array 10 20 30) 2 99) ; => #i64(10 20 99)"
],
"body": "Set the element at a given index. Uses copy-on-write -- the original array is unchanged unless it has a single reference.\n\n```sema\n(f64-array/set! (f64-array 1.0 2.0 3.0) 1 9.9) ; => #f64(1 9.9 3)\n(i64-array/set! (i64-array 10 20 30) 2 99) ; => #i64(10 20 99)\n```"
},
{
"name": "f64-array/sum",
"aliases": [
"i64-array/sum"
],
"module": "typed-arrays",
"section": "Aggregation",
"summary": "Sum all elements. Runs in a tight Rust loop with no boxing overhead.",
"examples": [
"(f64-array/sum (f64-array 1.0 2.0 3.0)) ; => 6.0\n(i64-array/sum (i64-array 1 2 3 4 5)) ; => 15"
],
"body": "Sum all elements. Runs in a tight Rust loop with no boxing overhead.\n\n```sema\n(f64-array/sum (f64-array 1.0 2.0 3.0)) ; => 6.0\n(i64-array/sum (i64-array 1 2 3 4 5)) ; => 15\n```"
},
{
"name": "f64-array?",
"aliases": [
"i64-array?"
],
"module": "typed-arrays",
"section": "Type Predicates",
"summary": "Test whether a value is a typed array.",
"examples": [
"(f64-array? (f64-array 1.0 2.0)) ; => #t\n(f64-array? '(1.0 2.0)) ; => #f\n(i64-array? (i64-array 1 2)) ; => #t"
],
"body": "Test whether a value is a typed array.\n\n```sema\n(f64-array? (f64-array 1.0 2.0)) ; => #t\n(f64-array? '(1.0 2.0)) ; => #f\n(i64-array? (i64-array 1 2)) ; => #t\n```"
},
{
"name": "i64-array",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an i64 array from values.",
"examples": [
"(i64-array 1 2 3) ; => #i64(1 2 3)\n(i64-array) ; => #i64()"
],
"body": "Create an i64 array from values.\n\n```sema\n(i64-array 1 2 3) ; => #i64(1 2 3)\n(i64-array) ; => #i64()\n```"
},
{
"name": "i64-array/from-list",
"module": "typed-arrays",
"section": "Construction",
"summary": "Convert a list of integers to an i64 array.",
"examples": [
"(i64-array/from-list '(10 20 30)) ; => #i64(10 20 30)"
],
"body": "Convert a list of integers to an i64 array.\n\n```sema\n(i64-array/from-list '(10 20 30)) ; => #i64(10 20 30)\n```"
},
{
"name": "i64-array/make",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an i64 array of a given length, optionally filled with a value (default `0`).",
"examples": [
"(i64-array/make 5) ; => #i64(0 0 0 0 0)\n(i64-array/make 3 42) ; => #i64(42 42 42)"
],
"body": "Create an i64 array of a given length, optionally filled with a value (default `0`).\n\n```sema\n(i64-array/make 5) ; => #i64(0 0 0 0 0)\n(i64-array/make 3 42) ; => #i64(42 42 42)\n```"
},
{
"name": "i64-array/range",
"module": "typed-arrays",
"section": "Construction",
"summary": "Create an i64 array from an integer range.",
"examples": [
"(i64-array/range 0 5) ; => #i64(0 1 2 3 4)\n(i64-array/range 0 10 2) ; => #i64(0 2 4 6 8)"
],
"body": "Create an i64 array from an integer range.\n\n```sema\n(i64-array/range 0 5) ; => #i64(0 1 2 3 4)\n(i64-array/range 0 10 2) ; => #i64(0 2 4 6 8)\n```"
},
{
"name": "vector-store/add",
"module": "vector-store",
"summary": "Add (or replace, by id) a document in the named vector store with its embedding bytevector and an arbitrary metadata value. Returns the document id.",
"params": [
{
"name": "name",
"type": "string"
},
{
"name": "id",
"type": "string"
},
{
"name": "embedding",
"type": "bytevector"
},
{
"name": "metadata"
}
],
"returns": "string",
"examples": [
"(vector-store/add \"docs\" \"doc-1\" (llm/embed \"hello\") {:title \"Greeting\"})"
],
"body": "Add (or replace, by id) a document in the named vector store with its embedding bytevector and an arbitrary metadata value. Returns the document id.\n\n```sema\n(vector-store/add \"docs\" \"doc-1\" (llm/embed \"hello\") {:title \"Greeting\"})\n```"
},
{
"name": "vector-store/count",
"module": "vector-store",
"summary": "Return the number of documents in the named vector store.",
"params": [
{
"name": "name",
"type": "string"
}
],
"returns": "int",
"examples": [
"(vector-store/count \"docs\") ; => 42"
],
"body": "Return the number of documents in the named vector store.\n\n```sema\n(vector-store/count \"docs\") ; => 42\n```"
},
{
"name": "vector-store/create",
"module": "vector-store",
"summary": "Create a new, empty in-memory vector store registered under the given name, returning the name. Use `vector-store/open` instead to load from or persist to disk.",
"params": [
{
"name": "name",
"type": "string"
}
],
"returns": "string",
"examples": [
"(vector-store/create \"docs\")"
],
"body": "Create a new, empty in-memory vector store registered under the given name, returning the name. Use `vector-store/open` instead to load from or persist to disk.\n\n```sema\n(vector-store/create \"docs\")\n```"
},
{
"name": "vector-store/delete",
"module": "vector-store",
"summary": "Remove the document with the given id from the named store. Returns true if a document was removed, false otherwise.",
"params": [
{
"name": "name",
"type": "string"
},
{
"name": "id",
"type": "string"
}
],
"returns": "bool",
"examples": [
"(vector-store/delete \"docs\" \"doc-1\")"
],
"body": "Remove the document with the given id from the named store. Returns true if a document was removed, false otherwise.\n\n```sema\n(vector-store/delete \"docs\" \"doc-1\")\n```"
},
{
"name": "vector-store/open",
"module": "vector-store",
"summary": "Open a vector store under the given name, loading it from the JSON file at path if it exists or creating an empty one otherwise, and associate the path so later `vector-store/save` calls can omit it. Returns the name.",
"params": [
{
"name": "name",
"type": "string"
},
{
"name": "path",
"type": "string"
}
],
"returns": "string",
"examples": [
"(vector-store/open \"docs\" \"docs.json\")"
],
"body": "Open a vector store under the given name, loading it from the JSON file at path if it exists or creating an empty one otherwise, and associate the path so later `vector-store/save` calls can omit it. Returns the name.\n\n```sema\n(vector-store/open \"docs\" \"docs.json\")\n```"
},
{
"name": "vector-store/save",
"module": "vector-store",
"summary": "Persist the named vector store to disk as JSON, writing atomically. The path is optional if the store already has an associated path (e.g. from `vector-store/open`); otherwise it must be supplied. Returns the path written.",
"params": [
{
"name": "name",
"type": "string"
},
{
"name": "path",
"type": "string"
}
],
"returns": "string",
"examples": [
"(vector-store/save \"docs\" \"docs.json\")"
],
"body": "Persist the named vector store to disk as JSON, writing atomically. The path is optional if the store already has an associated path (e.g. from `vector-store/open`); otherwise it must be supplied. Returns the path written.\n\n```sema\n(vector-store/save \"docs\" \"docs.json\")\n```"
},
{
"name": "vector-store/search",
"module": "vector-store",
"summary": "Return the top `k` documents in the named store ranked by cosine similarity to the query embedding. Each result is a map with `:id`, `:score`, and `:metadata`.",
"params": [
{
"name": "name",
"type": "string"
},
{
"name": "query",
"type": "bytevector"
},
{
"name": "k",
"type": "int"
}
],
"returns": "list",
"examples": [
"(vector-store/search \"docs\" (llm/embed \"greeting\") 5)"
],
"body": "Return the top `k` documents in the named store ranked by cosine similarity to the query embedding. Each result is a map with `:id`, `:score`, and `:metadata`.\n\n```sema\n(vector-store/search \"docs\" (llm/embed \"greeting\") 5)\n```"
},
{
"name": "&",
"module": "vectors",
"section": "Destructuring",
"summary": "Note: `tail` is a **list**, not a vector.",
"examples": [
"(let (([head second & tail] [1 2 3 4 5]))\n [head second tail])\n; => [1 2 (3 4 5)]"
],
"body": "```sema\n(let (([head second & tail] [1 2 3 4 5]))\n [head second tail])\n; => [1 2 (3 4 5)]\n```\n\nNote: `tail` is a **list**, not a vector."
},
{
"name": "first",
"module": "vectors",
"section": "Indexed Access",
"summary": "Return the first element of a vector (or list). Returns `nil` for empty vectors.",
"examples": [
"(first [1 2 3]) ; => 1\n(first []) ; => nil"
],
"body": "Return the first element of a vector (or list). Returns `nil` for empty vectors.\n\n```sema\n(first [1 2 3]) ; => 1\n(first []) ; => nil\n```"
},
{
"name": "length",
"aliases": [
"count",
"empty?"
],
"module": "vectors",
"section": "Predicates & Introspection",
"summary": "Vectors participate in Sema's generic collection functions:",
"examples": [
"(length [10 20 30]) ; => 3\n(count [10 20 30]) ; => 3\n(empty? []) ; => #t\n(empty? [1]) ; => #f"
],
"body": "Vectors participate in Sema's generic collection functions:\n\n```sema\n(length [10 20 30]) ; => 3\n(count [10 20 30]) ; => 3\n(empty? []) ; => #t\n(empty? [1]) ; => #f\n```"
},
{
"name": "list->vector",
"module": "vectors",
"section": "Conversion",
"summary": "Convert a list to a vector.",
"examples": [
"(list->vector '(1 2 3)) ; => [1 2 3]\n(list->vector '()) ; => []"
],
"body": "Convert a list to a vector.\n\n```sema\n(list->vector '(1 2 3)) ; => [1 2 3]\n(list->vector '()) ; => []\n```"
},
{
"name": "nth",
"module": "vectors",
"section": "Indexed Access",
"summary": "Return the element at index `n` (zero-based). Works on both lists and vectors.",
"examples": [
"(nth [10 20 30] 0) ; => 10\n(nth [10 20 30] 2) ; => 30",
"(nth [10 20 30] 3)\n; => error: index 3 out of bounds (length 3)"
],
"body": "Return the element at index `n` (zero-based). Works on both lists and vectors.\n\n```sema\n(nth [10 20 30] 0) ; => 10\n(nth [10 20 30] 2) ; => 30\n```\n\nOut of bounds is an error:\n\n```sema\n(nth [10 20 30] 3)\n; => error: index 3 out of bounds (length 3)\n```\n\nUse `first` for safe \"index 0\" access — it returns `nil` on empty sequences."
},
{
"name": "rest",
"module": "vectors",
"section": "Indexed Access",
"summary": "Return everything after the first element. **Preserves type** — vector in, vector out.",
"examples": [
"(rest [1 2 3]) ; => [2 3]\n(rest []) ; => []\n(rest [1]) ; => []"
],
"body": "Return everything after the first element. **Preserves type** — vector in, vector out.\n\n```sema\n(rest [1 2 3]) ; => [2 3]\n(rest []) ; => []\n(rest [1]) ; => []\n```"
},
{
"name": "vector",
"module": "vectors",
"section": "Construction",
"summary": "Create a vector from its arguments.",
"examples": [
"(vector 1 2 3) ; => [1 2 3]\n(vector) ; => []\n(vector \"a\" \"b\") ; => [\"a\" \"b\"]"
],
"body": "Create a vector from its arguments.\n\n```sema\n(vector 1 2 3) ; => [1 2 3]\n(vector) ; => []\n(vector \"a\" \"b\") ; => [\"a\" \"b\"]\n```"
},
{
"name": "vector->list",
"module": "vectors",
"section": "Conversion",
"summary": "Convert a vector to a list.",
"examples": [
"(vector->list [1 2 3]) ; => (1 2 3)\n(vector->list []) ; => ()"
],
"body": "Convert a vector to a list.\n\n```sema\n(vector->list [1 2 3]) ; => (1 2 3)\n(vector->list []) ; => ()\n```"
},
{
"name": "vector/cosine-similarity",
"module": "vectors",
"summary": "Compute the cosine similarity between two embedding vectors (dot product divided by the product of magnitudes). Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes. Returns `0.0` if either vector has zero magnitude.",
"params": [
{
"name": "a",
"type": "bytevector"
},
{
"name": "b",
"type": "bytevector"
}
],
"returns": "float",
"examples": [
"(vector/cosine-similarity\n (embedding/list->embedding '(1.0 0.0))\n (embedding/list->embedding '(1.0 0.0))) ; => 1.0"
],
"body": "Compute the cosine similarity between two embedding vectors (dot product divided by the product of magnitudes). Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes. Returns `0.0` if either vector has zero magnitude.\n\n```sema\n(vector/cosine-similarity\n (embedding/list->embedding '(1.0 0.0))\n (embedding/list->embedding '(1.0 0.0))) ; => 1.0\n```"
},
{
"name": "vector/distance",
"module": "vectors",
"summary": "Compute the Euclidean (L2) distance between two embedding vectors. Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes.",
"params": [
{
"name": "a",
"type": "bytevector"
},
{
"name": "b",
"type": "bytevector"
}
],
"returns": "float",
"examples": [
"(vector/distance\n (embedding/list->embedding '(0.0 0.0))\n (embedding/list->embedding '(3.0 4.0))) ; => 5.0"
],
"body": "Compute the Euclidean (L2) distance between two embedding vectors. Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes.\n\n```sema\n(vector/distance\n (embedding/list->embedding '(0.0 0.0))\n (embedding/list->embedding '(3.0 4.0))) ; => 5.0\n```"
},
{
"name": "vector/dot-product",
"module": "vectors",
"summary": "Compute the dot product of two embedding vectors. Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes.",
"params": [
{
"name": "a",
"type": "bytevector"
},
{
"name": "b",
"type": "bytevector"
}
],
"returns": "float",
"examples": [
"(vector/dot-product\n (embedding/list->embedding '(1.0 2.0 3.0))\n (embedding/list->embedding '(4.0 5.0 6.0))) ; => 32.0"
],
"body": "Compute the dot product of two embedding vectors. Each argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`). Both vectors must have the same, non-empty length that is a multiple of 8 bytes.\n\n```sema\n(vector/dot-product\n (embedding/list->embedding '(1.0 2.0 3.0))\n (embedding/list->embedding '(4.0 5.0 6.0))) ; => 32.0\n```"
},
{
"name": "vector/normalize",
"module": "vectors",
"summary": "Return a unit-length copy of an embedding vector (each component divided by the vector's magnitude). The argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`), non-empty with a length that is a multiple of 8 bytes. A zero vector is returned unchanged (all zeros).",
"params": [
{
"name": "v",
"type": "bytevector"
}
],
"returns": "bytevector",
"examples": [
";; (3.0 4.0) has magnitude 5, so it normalizes to (0.6 0.8)\n(embedding/->list (vector/normalize (embedding/list->embedding '(3.0 4.0))))\n; => (0.6 0.8)"
],
"body": "Return a unit-length copy of an embedding vector (each component divided by the vector's magnitude). The argument is a bytevector of packed little-endian f64 values (as produced by `embedding/list->embedding`), non-empty with a length that is a multiple of 8 bytes. A zero vector is returned unchanged (all zeros).\n\n```sema\n;; (3.0 4.0) has magnitude 5, so it normalizes to (0.6 0.8)\n(embedding/->list (vector/normalize (embedding/list->embedding '(3.0 4.0))))\n; => (0.6 0.8)\n```"
},
{
"name": "vector?",
"module": "vectors",
"section": "Predicates & Introspection",
"summary": "Test whether a value is a vector.",
"examples": [
"(vector? [1 2 3]) ; => #t\n(vector? '(1 2 3)) ; => #f\n(vector? 42) ; => #f"
],
"body": "Test whether a value is a vector.\n\n```sema\n(vector? [1 2 3]) ; => #t\n(vector? '(1 2 3)) ; => #f\n(vector? 42) ; => #f\n```"
},
{
"name": ":static",
"module": "web-server",
"section": "Static File Serving",
"summary": "Serve an entire directory of static files using the `:static` route type in `http/router`. Files are served with automatic MIME types, cache headers, and path traversal protection.",
"examples": [
"(define routes\n [[:static \"/assets\" \"./public\"]\n [:get \"/*\" handle-spa]])\n\n(http/serve (http/router routes) {:port 3000})",
"(define routes\n [[:static \"/assets\" \"./dist/assets\"]\n [:get \"/*\" (fn (_) (http/file \"./dist/index.html\"))]])\n\n(http/serve (http/router routes) {:port 3000})"
],
"body": "Serve an entire directory of static files using the `:static` route type in `http/router`. Files are served with automatic MIME types, cache headers, and path traversal protection.\n\n```sema\n(define routes\n [[:static \"/assets\" \"./public\"]\n [:get \"/*\" handle-spa]])\n\n(http/serve (http/router routes) {:port 3000})\n```\n\n```bash\n$ curl http://localhost:3000/assets/style.css\nbody { color: red; }\n\n$ curl -I http://localhost:3000/assets/style.css\nContent-Type: text/css\nCache-Control: public, max-age=3600\n```\n\nThe `:static` route takes a URL prefix and a directory path. Requests matching the prefix are mapped to files in the directory:\n\n- `GET /assets/style.css` → reads `./public/style.css`\n- `GET /assets/js/app.js` → reads `./public/js/app.js`\n- `GET /assets/` → reads `./public/index.html` (directory index)\n\n**Fallthrough**: If a file doesn't exist, the route does *not* match — the router continues to the next route. This enables SPA (single-page application) patterns where a catch-all route serves `index.html` for client-side routing:\n\n```sema\n(define routes\n [[:static \"/assets\" \"./dist/assets\"]\n [:get \"/*\" (fn (_) (http/file \"./dist/index.html\"))]])\n\n(http/serve (http/router routes) {:port 3000})\n```\n\n**Security**: Path traversal attempts (e.g. `../etc/passwd`) are rejected with a 400 response. Only GET and HEAD methods are accepted."
},
{
"name": "http/created",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 201 with a JSON-encoded body.",
"examples": [
"(http/created {:id 42 :name \"Ada\"})"
],
"body": "Return 201 with a JSON-encoded body.\n\n```sema\n(http/created {:id 42 :name \"Ada\"})\n```"
},
{
"name": "http/error",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return a custom status code with a JSON-encoded body.",
"examples": [
"(http/error 422 {:errors [\"Invalid email\" \"Name required\"]})\n(http/error 503 {:error \"Service unavailable\"})"
],
"body": "Return a custom status code with a JSON-encoded body.\n\n```sema\n(http/error 422 {:errors [\"Invalid email\" \"Name required\"]})\n(http/error 503 {:error \"Service unavailable\"})\n```"
},
{
"name": "http/file",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return a file from disk with automatic MIME type detection. The file is read on the I/O thread (not the evaluator), so it handles binary files efficiently.",
"examples": [
"(http/file \"public/index.html\")\n(http/file \"data/report.pdf\" \"application/pdf\") ; explicit content type"
],
"body": "Return a file from disk with automatic MIME type detection. The file is read on the I/O thread (not the evaluator), so it handles binary files efficiently.\n\n```sema\n(http/file \"public/index.html\")\n(http/file \"data/report.pdf\" \"application/pdf\") ; explicit content type\n```\n\nThe path is resolved relative to the current working directory. If the file doesn't exist, an error is raised. The MIME type is guessed from the file extension (e.g. `.html` → `text/html`, `.css` → `text/css`, `.js` → `application/javascript`)."
},
{
"name": "http/html",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 200 with `Content-Type: text/html`.",
"examples": [
"(http/html \"<h1>Hello</h1><p>Welcome to Sema.</p>\")"
],
"body": "Return 200 with `Content-Type: text/html`.\n\n```sema\n(http/html \"<h1>Hello</h1><p>Welcome to Sema.</p>\")\n```"
},
{
"name": "http/no-content",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 204 with an empty body.",
"examples": [
"(http/no-content)"
],
"body": "Return 204 with an empty body.\n\n```sema\n(http/no-content)\n```"
},
{
"name": "http/not-found",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 404 with a JSON-encoded body.",
"examples": [
"(http/not-found {:error \"User not found\"})"
],
"body": "Return 404 with a JSON-encoded body.\n\n```sema\n(http/not-found {:error \"User not found\"})\n```"
},
{
"name": "http/ok",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 200 with a JSON-encoded body.",
"examples": [
"(pprint (http/ok {:message \"success\"}))\n; => {:body \"{\"message\":\"success\"}\"\n; :headers {\"content-type\" \"application/json\"}\n; :status 200}\n\n(pprint (http/ok [1 2 3]))\n; => {:body \"[1,2,3]\" :headers {\"content-type\" \"application/json\"} :status 200}"
],
"body": "Return 200 with a JSON-encoded body.\n\n```sema\n(pprint (http/ok {:message \"success\"}))\n; => {:body \"{\"message\":\"success\"}\"\n; :headers {\"content-type\" \"application/json\"}\n; :status 200}\n\n(pprint (http/ok [1 2 3]))\n; => {:body \"[1,2,3]\" :headers {\"content-type\" \"application/json\"} :status 200}\n```"
},
{
"name": "http/redirect",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return a 302 redirect to a URL.",
"examples": [
"(http/redirect \"https://example.com/login\")"
],
"body": "Return a 302 redirect to a URL.\n\n```sema\n(http/redirect \"https://example.com/login\")\n```"
},
{
"name": "http/router",
"module": "web-server",
"section": "Routing",
"summary": "Create a handler function from a list of route definitions. Each route is a vector of `[method pattern handler]`.",
"examples": [
"(define routes\n [[:get \"/\" handle-home]\n [:get \"/users/:id\" handle-user]\n [:post \"/users\" handle-create]\n [:any \"/echo\" handle-echo]])\n\n(define app (http/router routes))\n(http/serve app {:port 3000})"
],
"body": "Create a handler function from a list of route definitions. Each route is a vector of `[method pattern handler]`.\n\n```sema\n(define routes\n [[:get \"/\" handle-home]\n [:get \"/users/:id\" handle-user]\n [:post \"/users\" handle-create]\n [:any \"/echo\" handle-echo]])\n\n(define app (http/router routes))\n(http/serve app {:port 3000})\n```\n\nSupported methods: `:get`, `:post`, `:put`, `:patch`, `:delete`, `:any` (matches all methods), `:ws` (WebSocket upgrade), and `:static` (static file directory).\n\nRoutes are matched top-to-bottom — first match wins. Unmatched routes return 404."
},
{
"name": "http/serve",
"module": "web-server",
"section": "Serving",
"summary": "Start an HTTP server. Takes a handler function and an optional options map. The handler receives a request map and returns a response map. This function blocks — it becomes the server's run loop.",
"examples": [
"(http/serve handler)\n(http/serve handler {:port 3000})\n(http/serve handler {:port 8080 :host \"127.0.0.1\"})"
],
"body": "Start an HTTP server. Takes a handler function and an optional options map. The handler receives a request map and returns a response map. This function blocks — it becomes the server's run loop.\n\n```sema\n(http/serve handler)\n(http/serve handler {:port 3000})\n(http/serve handler {:port 8080 :host \"127.0.0.1\"})\n```\n\n| Option | Default | Description |\n| ------- | ----------- | ------------------ |\n| `:port` | `3000` | TCP port to bind |\n| `:host` | `\"0.0.0.0\"` | Address to bind to |\n\nThe handler is any function `(request-map -> response-map)`. This can be a plain function, a router, or a middleware-wrapped stack."
},
{
"name": "http/stream",
"module": "web-server",
"section": "SSE Streaming",
"summary": "Return a Server-Sent Events stream. Takes a handler function that receives a `send` callback.",
"examples": [
"(define (handle-events req)\n (http/stream\n (fn (send)\n (send \"connected\")\n (sleep 1000)\n (send \"update 1\")\n (sleep 1000)\n (send \"update 2\"))))",
";; Route it like any other handler\n(define routes\n [[:get \"/events\" handle-events]])"
],
"body": "Return a Server-Sent Events stream. Takes a handler function that receives a `send` callback.\n\n```sema\n(define (handle-events req)\n (http/stream\n (fn (send)\n (send \"connected\")\n (sleep 1000)\n (send \"update 1\")\n (sleep 1000)\n (send \"update 2\"))))\n```\n\nThe stream stays open as long as the handler is running. When the handler returns, the stream closes.\n\n```sema\n;; Route it like any other handler\n(define routes\n [[:get \"/events\" handle-events]])\n```\n\n```bash\n$ curl -N http://localhost:3000/events\ndata: connected\n\ndata: update 1\n\ndata: update 2\n```"
},
{
"name": "http/text",
"module": "web-server",
"section": "Response Helpers",
"summary": "Return 200 with `Content-Type: text/plain`.",
"examples": [
"(http/text \"OK\")"
],
"body": "Return 200 with `Content-Type: text/plain`.\n\n```sema\n(http/text \"OK\")\n```"
},
{
"name": "http/websocket",
"module": "web-server",
"section": "WebSocket",
"summary": "Handle bidirectional WebSocket connections. Takes a handler function that receives a connection map with `:send`, `:recv`, and `:close` functions.",
"examples": [
"(define (handle-ws conn)\n (let ((msg ((:recv conn))))\n (when msg\n ((:send conn) (string/append \"echo: \" msg))\n (handle-ws conn))))"
],
"body": "Handle bidirectional WebSocket connections. Takes a handler function that receives a connection map with `:send`, `:recv`, and `:close` functions.\n\n```sema\n(define (handle-ws conn)\n (let ((msg ((:recv conn))))\n (when msg\n ((:send conn) (string/append \"echo: \" msg))\n (handle-ws conn))))\n```\n\nThe connection map:\n\n| Key | Description |\n| -------- | -------------------------------------------------------- |\n| `:send` | `(send message)` — Send a string to the client |\n| `:recv` | `(recv)` — Block until a message arrives, `nil` on close |\n| `:close` | `(close)` — Close the connection |"
},
{
"name": "tools->routes",
"module": "web-server",
"section": "Routing",
"summary": "Convert a list of tool definitions (from `deftool`) into HTTP routes. Each tool gets a `POST /tools/<name>` endpoint that runs the tool handler against the request's JSON body, plus a `/tools/<name>/schema` endpoint exposing its parameter schema.",
"params": [
{
"name": "tools",
"type": "list | vector"
}
],
"examples": [
"(tools->routes [my-tool other-tool])\n; => a list of route handlers for use with the web server"
],
"body": "Convert a list of tool definitions (from `deftool`) into HTTP routes. Each tool gets a `POST /tools/<name>` endpoint that runs the tool handler against the request's JSON body, plus a `/tools/<name>/schema` endpoint exposing its parameter schema.\n\n```sema\n(tools->routes [my-tool other-tool])\n; => a list of route handlers for use with the web server\n```"
}
]
}