Add definitive Jerboa language reference
ober
c9cdc36cafe4b02feaf57001bb3633a23abe2d60
new file mode 100644 --- /dev/null +++ b/JERBOA-LANG.md @@ -0,0 +1,1144 @@ +# The Jerboa Programming Language + +Jerboa is a Scheme dialect built on Chez Scheme. It is Gerbil-inspired but its own +language — with features that differ from and exceed Gerbil. All user-facing code is +written in `.ss` files. The `.sls` files are implementation internals. + +## Quick Start + +```scheme +;; hello.ss +(import (jerboa prelude)) + +(def (main) + (def name "world") + (displayln (str "Hello, " name "!"))) + +(main) +``` + +Run with: +``` +scheme --libdirs lib --script hello.ss +``` + +One import gives you the entire language: +```scheme +(import (jerboa prelude)) +``` + +--- + +## Table of Contents + +1. [File Structure](#file-structure) +2. [Reader Syntax](#reader-syntax) +3. [Definitions](#definitions) +4. [Data Structures](#data-structures) +5. [Pattern Matching](#pattern-matching) +6. [Control Flow](#control-flow) +7. [Error Handling](#error-handling) +8. [Result Types](#result-types) +9. [Iterators](#iterators) +10. [Threading Macros](#threading-macros) +11. [Ergonomic Typing](#ergonomic-typing) +12. [Hash Tables](#hash-tables) +13. [Functions & Combinators](#functions--combinators) +14. [Standard Library](#standard-library) +15. [FFI](#ffi) +16. [Differences from Gerbil](#differences-from-gerbil) +17. [Differences from Chez Scheme](#differences-from-chez-scheme) + +--- + +## File Structure + +A Jerboa `.ss` file has this shape: + +```scheme +(import (jerboa prelude)) +;; Optional extra imports for modules not in the prelude: +;; (import (std net request)) +;; (import (std db sqlite)) + +;; Definitions +(def (my-function x y) + (+ x y)) + +;; Top-level code +(displayln (my-function 1 2)) +``` + +Key points: +- **No `(library ...)` wrapper** — that's for `.sls` internals only +- **`(import (jerboa prelude))`** gives you everything: core macros, runtime, + result types, datetime, iterators, CSV, pretty-printer, JSON, paths, strings, + lists, alists, hash tables, functional combinators, ergo typing, FFI, and more +- For specialized modules not in the prelude, add extra imports + +--- + +## Reader Syntax + +Jerboa extends the Chez Scheme reader with Gerbil-inspired syntax. + +### Square Brackets → List Literals + +```scheme +[1 2 3] ;; → (list 1 2 3) +[] ;; → (list) +[a [b c]] ;; → (list a (list b c)) +``` + +### Curly Braces → Method Dispatch + +```scheme +{draw canvas} ;; → (~ canvas 'draw) +{move sprite 10 20} ;; → (~ sprite 'move 10 20) +{name person} ;; → (~ person 'name) +``` + +### Keywords (trailing colon) + +```scheme +name: ;; → keyword object #:name +color: ;; → keyword object #:color +(keyword? name:) ;; → #t +``` + +### Module Paths (Gerbil-style) + +```scheme +:std/sort ;; → (std sort) +:std/text/json ;; → (std text json) +:std/misc/string ;; → (std misc string) +``` + +Both forms work in `import`: +```scheme +(import :std/net/request) ;; Gerbil style +(import (std net request)) ;; R6RS style — both are equivalent +``` + +### Heredoc Strings + +```scheme +#<<END +This is a multi-line +string literal. +END +``` + +### Standard Scheme Syntax + +All standard Chez Scheme reader features work: +- `'expr` — quote +- `` `expr `` — quasiquote, with `,expr` unquote and `,@expr` splicing +- `#(1 2 3)` — vector +- `#u8(1 2 3)` — bytevector +- `#\space`, `#\newline`, `#\x41` — characters +- `#xFF`, `#b1010`, `#o77` — number radix prefixes +- `#;expr` — datum comment (skip next form) +- `#| ... |#` — block comment (nestable) +- `#&value` — box +- `#!void`, `#!eof` — void and eof literals + +--- + +## Definitions + +### `def` — Define Variables and Functions + +```scheme +;; Simple binding +(def x 42) +(def name "Alice") + +;; Function +(def (add a b) + (+ a b)) + +;; Optional parameters (default values) +(def (greet name (greeting "hello")) + (str greeting ", " name "!")) + +(greet "Bob") ;; → "hello, Bob!" +(greet "Bob" "howdy") ;; → "howdy, Bob!" + +;; Rest arguments +(def (first-and-rest first . rest) + (list first rest)) + +;; Type-checked parameters (via ergo) +(def (add (x : number?) (y : number?)) : number? + (+ x y)) +``` + +### `def*` — Multiple Arities + +```scheme +(def* greet + ((name) (greet name "hello")) + ((name greeting) (str greeting ", " name "!"))) +``` + +### `defrule` / `defrules` — Define Macros + +```scheme +;; Single-pattern macro +(defrule (swap! a b) + (let ((tmp a)) (set! a b) (set! b tmp))) + +;; Multi-pattern macro +(defrules my-or () + ((_ a) a) + ((_ a b ...) (let ((t a)) (if t t (my-or b ...))))) +``` + +--- + +## Data Structures + +### `defstruct` — Record Types + +```scheme +(defstruct point (x y)) + +;; Generated: +;; make-point — constructor +;; point? — predicate +;; point-x — accessor +;; point-y — accessor +;; point-x-set! — mutator +;; point-y-set! — mutator +;; point::t — record type descriptor + +(def p (make-point 3 4)) +(point-x p) ;; → 3 +(point-y-set! p 10) +(point-y p) ;; → 10 +``` + +### Inheritance + +```scheme +(defstruct shape (color)) +(defstruct (circle shape) (radius)) + +(def c (make-circle "red" 5)) +(shape-color c) ;; → "red" (inherited field) +(circle-radius c) ;; → 5 +(shape? c) ;; → #t +(circle? c) ;; → #t +``` + +Only **single inheritance** is supported. + +### `defclass` — Alias for `defstruct` + +```scheme +(defclass point (x y)) ;; same as defstruct +(defclass (circle shape) (r)) ;; same as defstruct with parent +``` + +### `defrecord` — Struct with Pretty Printing + +```scheme +(defrecord person (name age)) + +;; Same as defstruct plus: +;; person->alist — convert to association list +;; Custom printer: #<person name="Alice" age=30> +``` + +### `defmethod` — Method Dispatch + +```scheme +(defstruct circle (radius)) +(defstruct rect (width height)) + +(defmethod (area (self circle)) + (* 3.14159 (expt (circle-radius self) 2))) + +(defmethod (area (self rect)) + (* (rect-width self) (rect-height self))) + +;; Call with ~ or {} syntax +(def c (make-circle 5)) +(~ c 'area) ;; → 78.53975 +{area c} ;; → 78.53975 (same thing) + +;; Methods with extra args +(defmethod (move (self point) dx dy) + (make-point (+ (point-x self) dx) + (+ (point-y self) dy))) + +{move p 10 20} ;; → new point shifted by (10, 20) +``` + +Method dispatch walks the inheritance chain — a method defined on `shape` +works on `circle` too. + +### `define-enum` — Enumeration Types + +```scheme +(define-enum color (red green blue)) + +;; Generated: +;; color-red → 0 +;; color-green → 1 +;; color-blue → 2 +;; color? — predicate (checks 0..2) +;; color->name — number to string +;; name->color — string to number +``` + +--- + +## Pattern Matching + +### `match` + +```scheme +(match value + ;; Literal patterns + (0 "zero") + (#t "true") + ("hello" "greeting") + + ;; Variable binding + (x (str "got: " x))) + +;; List destructuring +(match (list 1 2 3) + ((list a b c) (+ a b c))) ;; → 6 + +;; Nested patterns +(match '(1 (2 3)) + ((list a (list b c)) (+ a b c))) ;; → 6 + +;; Cons patterns +(match '(1 2 3) + ((cons head tail) head)) ;; → 1 + +;; Predicate patterns +(match 42 + ((? string?) "a string") + ((? number?) "a number")) ;; → "a number" + +;; Guards +(match x + (n (where (> n 0)) "positive") + (n (where (< n 0)) "negative") + (_ "zero")) + +;; Logical combinators +(match x + ((and (? number?) (? positive?)) "positive number") + ((or "yes" "y" "true") #t) + ((not #f) "truthy")) + +;; Wildcard +(match x + (_ "anything")) + +;; View patterns — apply function, match result +(match "123" + ((=> string->number n) (+ n 1))) ;; → 124 + +;; Struct patterns (requires define-match-type registration) +(define-match-type point point? point-x point-y) +(match (make-point 3 4) + ((point x y) (+ x y))) ;; → 7 +``` + +### `match/strict` — Exhaustiveness-Checked Matching + +```scheme +(define-sealed-hierarchy shape + (circle circle? circle-radius) + (rect rect? rect-width rect-height)) + +(match/strict shape my-shape + ((circle r) (* 3.14 r r)) + ((rect w h) (* w h))) +;; Warns at runtime if a variant is missing +``` + +### `define-active-pattern` — Custom Extractors + +```scheme +(define-active-pattern (even? n) + (and (number? n) (zero? (mod n 2)) n)) + +(match 42 + ((even? n) (str n " is even"))) +``` + +--- + +## Control Flow + +### Conditionals + +```scheme +;; Standard +(if test then else) +(cond (test1 expr1) (test2 expr2) (else default)) +(when test body ...) +(unless test body ...) + +;; Anaphoric — binds result to `it` +(awhen (find-user name) + (send-email it)) + +(aif (lookup key) + (use it) + (handle-missing)) + +;; Conditional binding +(when-let (x (get-thing)) + (process x)) + +(if-let (user (find-user id)) + (greet user) + (show-login)) +``` + +### Loops + +```scheme +;; Counted loop +(dotimes (i 10) + (displayln i)) ;; prints 0..9 + +;; While/until +(while (< x 10) + (set! x (+ x 1))) + +(until (> x 10) + (set! x (+ x 1))) + +;; Iterators (see Iterators section) +(for ((x (in-range 10))) + (displayln x)) +``` + +### `assert!` + +```scheme +(assert! (> x 0)) ;; raises error if false +(assert! (> x 0) "x must be positive") ;; with message +``` + +--- + +## Error Handling + +### `try` / `catch` / `finally` + +```scheme +;; Basic try/catch +(try + (risky-operation) + (catch (e) + (displayln "Error: " (error-message e)))) + +;; With predicate +(try + (/ 1 0) + (catch (error? e) + (displayln "caught: " e))) + +;; With finally (always runs) +(try + (def port (open-input-file "data.txt")) + (process port) + (catch (e) + (displayln "failed")) + (finally + (close-input-port port))) +``` + +### Resource Management + +```scheme +;; Guaranteed cleanup +(unwind-protect + (do-work) + (cleanup)) + +;; Resource lifecycle +(with-resource (port (open-input-file "f.txt") close-input-port) + (read-all-as-string port)) + +;; Mutex +(with-lock my-mutex + (modify-shared-state)) +``` + +--- + +## Result Types + +Rust-inspired `Result<T,E>` for composable error handling without exceptions. + +### Constructors & Predicates + +```scheme +(ok 42) ;; success value +(err "not found") ;; error value + +(ok? (ok 42)) ;; → #t +(err? (err "x")) ;; → #t +(result? (ok 1)) ;; → #t +``` + +### Extracting Values + +```scheme +(unwrap (ok 42)) ;; → 42 +(unwrap (err "x")) ;; raises error! + +(unwrap-or (err "x") 0) ;; → 0 (safe default) +(unwrap-or-else (err "x") + (lambda () (compute-default))) ;; → lazy default + +(unwrap-err (err "bad")) ;; → "bad" +``` + +### Transforming Results + +```scheme +;; Map over ok values +(map-ok (lambda (x) (* x 2)) (ok 5)) ;; → (ok 10) +(map-ok (lambda (x) (* x 2)) (err "x")) ;; → (err "x") + +;; Map over error values +(map-err string-upcase (err "bad")) ;; → (err "BAD") + +;; Monadic bind (chain operations) +(and-then (ok 5) (lambda (x) + (if (> x 0) (ok (* x 2)) (err "negative")))) +;; → (ok 10) + +;; Error recovery +(or-else (err "bad") (lambda (e) (ok 0))) ;; → (ok 0) +``` + +### Exception ↔ Result Conversion + +```scheme +;; Wrap exceptions as err +(try-result (/ 1 0)) ;; → (err <condition>) +(try-result* (/ 1 0)) ;; → (err "...message string...") +(try-result (+ 1 2)) ;; → (ok 3) +``` + +### Collection Operations + +```scheme +;; All-or-nothing +(sequence-results (list (ok 1) (ok 2) (ok 3))) +;; → (ok (1 2 3)) + +(sequence-results (list (ok 1) (err "x") (ok 3))) +;; → (err "x") + +;; Partition successes and failures +(results-partition (list (ok 1) (err "a") (ok 2))) +;; → ((1 2) . ("a")) + +;; Filter +(filter-ok (list (ok 1) (err "a") (ok 2))) ;; → (1 2) +(filter-err (list (ok 1) (err "a") (ok 2))) ;; → ("a") +``` + +### Result-Aware Threading + +```scheme +(->? (ok 10) (+ 5) (* 2)) ;; → (ok 30) +(->? (err "x") (+ 5) (* 2)) ;; → (err "x") — short circuits +(->>? (ok 10) (- 3)) ;; → (ok -7) — thread last +``` + +--- + +## Iterators + +### `for` — Side Effects + +```scheme +(for ((x (in-range 5))) + (displayln x)) +;; prints 0 1 2 3 4 + +;; Multiple iterators (zipped, stops at shortest) +(for ((name (in-list '("Alice" "Bob"))) + (age (in-list '(30 25)))) + (displayln name ": " age)) +``` + +### `for/collect` — Build a List + +```scheme +(for/collect ((x (in-range 5))) + (* x x)) +;; → (0 1 4 9 16) +``` + +### `for/fold` — Accumulate + +```scheme +(for/fold ((sum 0)) ((x (in-range 10))) + (+ sum x)) +;; → 45 +``` + +### `for/or` — First Truthy + +```scheme +(for/or ((x (in-list '(1 -2 3 -4)))) + (and (negative? x) x)) +;; → -2 +``` + +### `for/and` — All Truthy + +```scheme +(for/and ((x (in-list '(2 4 6)))) + (even? x)) +;; → #t +``` + +### Iterator Constructors + +| Constructor | Description | Example | +|-------------|-------------|---------| +| `(in-list lst)` | Iterate over list | `(in-list '(a b c))` | +| `(in-vector vec)` | Iterate over vector | `(in-vector #(1 2 3))` | +| `(in-string str)` | Iterate over chars | `(in-string "abc")` | +| `(in-range end)` | 0 to end-1 | `(in-range 5)` → 0..4 | +| `(in-range start end)` | start to end-1 | `(in-range 2 5)` → 2..4 | +| `(in-range start end step)` | with step | `(in-range 0 10 2)` → 0,2,4,6,8 | +| `(in-hash-keys ht)` | Hash table keys | | +| `(in-hash-values ht)` | Hash table values | | +| `(in-hash-pairs ht)` | Key-value pairs | `(key . val)` | +| `(in-naturals)` | 0, 1, 2, ... | Bounded to 100k | +| `(in-naturals start)` | start, start+1, ... | | +| `(in-indexed lst)` | `(idx . elem)` pairs | | +| `(in-port [port [reader]])` | Read datums | | +| `(in-lines [port])` | Read lines | | +| `(in-chars [port])` | Read characters | | +| `(in-bytes [port])` | Read bytes | | +| `(in-producer thunk [sentinel])` | Call thunk until done | | + +--- + +## Threading Macros + +### `->` Thread First + +Insert value as **first** argument at each step: + +```scheme +(-> 10 + (+ 5) ;; (+ 10 5) → 15 + (* 2)) ;; (* 15 2) → 30 +``` + +### `->>` Thread Last + +Insert value as **last** argument: + +```scheme +(->> '(1 2 3 4 5) + (filter even?) ;; (filter even? '(...)) → (2 4) + (map (cut * <> 10)) ;; (map ... '(2 4)) → (20 40) + (apply +)) ;; → 60 +``` + +### `as->` Thread with Explicit Name + +```scheme +(as-> 1 x + (+ x 10) ;; → 11 + (* x 2) ;; → 22 + (- 100 x)) ;; → 78 +``` + +### `some->` / `some->>` Short-Circuit on `#f` + +```scheme +(some-> user + (get-address) ;; returns #f if no address + (get-city)) ;; skipped if previous was #f +;; → city or #f +``` + +### `cond->` / `cond->>` Conditional Steps + +```scheme +(cond-> base-query + include-deleted? (add-filter "deleted = true") + limit (add-limit limit)) +``` + +--- + +## Ergonomic Typing + +### `:` Type Cast + +```scheme +(: value predicate?) ;; checked cast — raises if wrong type +(: x number?) ;; asserts x is a number +``` + +### `using` — Typed Bindings with Dot Access + +```scheme +(defstruct point (x y)) + +(using (p (make-point 3 4) : point?) + (+ p.x p.y)) ;; p.x → (point-x p) +;; → 7 + +;; Multiple bindings +(using ((p (make-point 1 2) : point?) + (c (make-circle 5) : circle?)) + (displayln p.x c.radius)) + +;; `as` skips the type check (trust mode) +(using (p some-expr as point?) + p.x) +``` + +### Contract Predicates + +```scheme +;; Predicate factory — returns a predicate +((list-of? number?) '(1 2 3)) ;; → #t +((list-of? string?) '(1 2 3)) ;; → #f + +;; Maybe — accepts #f or matching value +((maybe string?) "hello") ;; → #t +((maybe string?) #f) ;; → #t +((maybe string?) 42) ;; → #f +``` + +--- + +## Hash Tables + +### Construction + +```scheme +;; Empty +(def ht (make-hash-table)) + +;; From pairs (literal macro) +(def ht (hash-literal ("name" "Alice") ("age" 30))) + +;; From list of pairs +(def ht (list->hash-table '(("a" . 1) ("b" . 2)))) + +;; From property list +(def ht (plist->hash-table '("name" "Alice" "age" 30))) +``` + +### Access + +```scheme +(hash-ref ht "name") ;; → "Alice" (error if missing) +(hash-ref ht "missing" "default") ;; → "default" +(hash-get ht "name") ;; → "Alice" or #f +(hash-key? ht "name") ;; → #t +``` + +### Mutation + +```scheme +(hash-put! ht "email" "alice@example.com") +(hash-update! ht "age" add1) +(hash-remove! ht "email") +(hash-merge! target source) ;; merge source into target +``` + +### Iteration + +```scheme +(hash-for-each (lambda (k v) (displayln k ": " v)) ht) +(hash-map (lambda (k v) (cons k (add1 v))) ht) +(hash-fold (lambda (k v acc) (+ acc v)) 0 ht) + +;; With iterators +(for ((k (in-hash-keys ht))) + (displayln k)) +(for (((k . v) (in-hash-pairs ht))) + (displayln k " → " v)) +``` + +### Conversion + +```scheme +(hash->list ht) ;; → ((key . val) ...) +(hash->plist ht) ;; → (key val key val ...) +(hash-keys ht) ;; → (key ...) +(hash-values ht) ;; → (val ...) +(hash-length ht) ;; → count +(hash-copy ht) ;; → shallow copy +``` + +### Destructuring + +```scheme +(let-hash ht (name age) + (displayln name " is " age)) +``` + +--- + +## Functions & Combinators + +### Partial Application + +```scheme +;; cut / cute (SRFI-26) — <> marks slots +((cut + 1 <>) 5) ;; → 6 +((cut map <> '(1 2 3)) add1) ;; → (2 3 4) +((cut string-append <> "!" <>) "hi" "?") ;; → "hi!?" + +;; partial (Clojure-style) +((partial + 10) 5) ;; → 15 +``` + +### Composition + +```scheme +((compose add1 add1) 5) ;; → 7 +((comp add1 (* 2)) 5) ;; same (alias) +((complement even?) 3) ;; → #t +((negate even?) 3) ;; → #t +((constantly 42) 'anything) ;; → 42 +(identity 42) ;; → 42 +((flip cons) '(2 3) 1) ;; → (1 2 3) +``` + +### Higher-Order Utilities + +```scheme +((conjoin positive? even?) 4) ;; → #t (both true) +((disjoin zero? negative?) -1) ;; → #t (either true) +((every-pred number? positive?) 5) ;; → #t +((some-fn string? number?) "hi") ;; → #t + +((curry + 1) 2) ;; → 3 +((juxt min max) 3 1 4 1 5) ;; → (1 5) +((fnil + 0 0) #f 5) ;; → 5 (replaces #f with 0) + +(def slow-fib (memo-proc fib)) ;; memoize a procedure +``` + +--- + +## Standard Library + +Everything below is available from `(import (jerboa prelude))`. + +### Strings — `(std misc string)` + +```scheme +(string-split "a,b,c" #\,) ;; → ("a" "b" "c") +(string-join '("a" "b" "c") ",") ;; → "a,b,c" +(string-trim " hello ") ;; → "hello" +(string-prefix? "hel" "hello") ;; → #t +(string-suffix? "llo" "hello") ;; → #t +(string-contains "hello" "ell") ;; → 1 (index) +(string-index "hello" #\l) ;; → 2 +(string-empty? "") ;; → #t +(string-match? "^[0-9]+$" "123") ;; → #t +(string-find "[0-9]+" "abc123") ;; → "123" +(string-find-all "[0-9]+" "a1b2");; → ("1" "2") +(str "age: " 25 "!") ;; → "age: 25!" (auto-coerce) +``` + +### Lists — `(std misc list)` + +```scheme +(flatten '(1 (2 (3)))) ;; → (1 2 3) +(unique '(1 2 2 3 3)) ;; → (1 2 3) +(take '(1 2 3 4 5) 3) ;; → (1 2 3) +(drop '(1 2 3 4 5) 3) ;; → (4 5) +(take-last '(1 2 3 4) 2) ;; → (3 4) +(drop-last '(1 2 3 4) 2) ;; → (1 2) +(every number? '(1 2 3)) ;; → #t +(any negative? '(1 -2 3)) ;; → #t +(filter-map (lambda (x) (and (> x 0) (* x 2))) '(-1 2 -3 4)) +;; → (4 8) +(group-by even? '(1 2 3 4 5)) +;; → ((#t 2 4) (#f 1 3 5)) +(zip '(1 2 3) '(a b c)) ;; → ((1 a) (2 b) (3 c)) +(frequencies '(a b a c b a)) ;; → ((a . 3) (b . 2) (c . 1)) +(partition even? '(1 2 3 4 5)) ;; → ((2 4) (1 3 5)) +(interleave '(1 2 3) '(a b c)) ;; → (1 a 2 b 3 c) +(interpose ", " '("a" "b" "c")) ;; → ("a" ", " "b" ", " "c") +(mapcat (lambda (x) (list x x)) '(1 2 3)) ;; → (1 1 2 2 3 3) +(distinct '(1 2 1 3 2)) ;; → (1 2 3) +(keep (lambda (x) (and (> x 0) x)) '(-1 2 -3 4)) ;; → (2 4) +(split-at '(1 2 3 4 5) 3) ;; → ((1 2 3) (4 5)) +(reductions + 0 '(1 2 3)) ;; → (0 1 3 6) +(append-map (lambda (x) (list x x)) '(1 2)) ;; → (1 1 2 2) +(snoc '(1 2) 3) ;; → (1 2 3) +``` + +### Association Lists — `(std misc alist)` + +```scheme +;; Construction +(alist (name "Alice") (age 30)) ;; → ((name . "Alice") (age . 30)) + +;; Access (q=eq?, v=eqv?, default=equal?) +(aget '((name . "Alice")) 'name) ;; → "Alice" +(agetq '((x . 1)) 'x) ;; → 1 + +;; Mutation +(aset! '((x . 1)) 'x 2) ;; → ((x . 2)) + +;; Property lists +(pgetq '(x 1 y 2) 'y) ;; → 2 +``` + +### Sorting — `(std sort)` + +```scheme +(sort '(3 1 2) <) ;; → (1 2 3) +(sort! vec <) ;; in-place on vectors +(stable-sort lst string<?) ;; preserves order of equal elements +``` + +### Formatting — `(std format)` + +```scheme +(format "~a is ~a" "Alice" 30) ;; → "Alice is 30" +(printf "count: ~a\n" 42) ;; print to stdout +(eprintf "error: ~a\n" msg) ;; print to stderr +``` + +### JSON — `(std text json)` + +```scheme +(def data (string->json-object "{\"name\":\"Alice\",\"age\":30}")) +(hash-ref data "name") ;; → "Alice" + +(json-object->string (hash-literal ("x" 1) ("y" 2))) +;; → "{\"x\":1,\"y\":2}" + +;; Port-based +(read-json port) +(write-json obj port) +``` + +### Paths — `(std os path)` + +```scheme +(path-join "/home" "user" "file.txt") ;; → "/home/user/file.txt" +(path-directory "/home/user/f.txt") ;; → "/home/user" +(path-extension "/home/user/f.txt") ;; → "txt" +(path-strip-extension "file.txt") ;; → "file" +(path-absolute? "/home") ;; → #t +(path-expand "~/file") ;; → "/home/user/file" +``` + +### File I/O — `(std misc ports)` + +```scheme +(read-file-string "data.txt") ;; → entire file as string +(read-file-lines "data.txt") ;; → list of lines +(write-file-string "out.txt" "hello") ;; write string to file +(read-all-as-string port) ;; read port to string +``` + +### CSV — `(std csv)` + +```scheme +(csv->alists "name,age\nAlice,30\nBob,25") +;; → (((name . "Alice") (age . "30")) +;; ((name . "Bob") (age . "25"))) + +(read-csv-file "data.csv") ;; → list of row lists +(write-csv-file "out.csv" '(("a" "b") ("1" "2"))) +(rows->csv-string '(("x" "y") ("1" "2"))) +``` + +### DateTime — `(std datetime)` + +```scheme +(def now (datetime-now)) +(datetime-year now) ;; → 2026 +(datetime->iso8601 now) ;; → "2026-03-27T22:13:43Z" + +;; Construction +(make-datetime 2026 3 27 12 0 0) +(make-date 2026 3 27) +(make-time 12 30 0) + +;; Parsing +(parse-datetime "2026-03-27T12:00:00Z") +(parse-date "2026-03-27") + +;; Arithmetic +(datetime-add now (make-duration 3600 0)) ;; +1 hour +(datetime-diff dt1 dt2) ;; → duration + +;; Comparison +(datetime<? dt1 dt2) +(datetime-min dt1 dt2) +(datetime-clamp dt lower upper)