These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 8/19/2026.

YAML

@korulang/yaml@0.0.1

Streaming YAML parser for Koru wrapping libyaml with phantom obligation types

yaml/index.kz · 8 tors

@korulang/yaml - Streaming YAML parser for Koru · 52 more lines
@korulang/yaml - Streaming YAML parser for Koru Wraps libyaml (the reference C implementation — the same engine behind PyYAML, Ruby's Psych, and most language YAML bindings) and lifts its notoriously leak-prone streaming event API into Koru's phantom obligation system. libyaml's parser is a two-level resource machine, and BOTH levels leak if you forget a single call: * the parser itself must be `yaml_parser_delete`d, and * EVERY event returned by `yaml_parser_parse` must be `yaml_event_delete`d (each event owns heap-allocated scalar / tag / anchor buffers). The classic footgun is the second one: a `while` loop that parses events but forgets `yaml_event_delete` inside the loop body leaks on every single event. In C that is a silent, unbounded leak. Here it is a compile error. This edition fuses the two resources into ONE obligation-bearing handle — a `Cursor<live!>` — exactly the way the language's own resource-lifecycle idiom fuses a connection and its open transaction into a single tracked state (koru tests/regression/.../2104 `app/db`: `Transaction<active!>`). A live cursor means "the parser is open AND a current event is undeleted"; you thread that one obligation, and the compiler will not let you drop it: * `advance` consumes a live cursor and mints a fresh one (delete-then-parse) * `finish` consumes a live cursor and fully cleans up (delete + close) Forgetting either — leaking the cursor — does not compile. USAGE (straight-line; see examples/ for a walked document): ~import libs/yaml ~libs/yaml:open(input: yaml_text) | ok p |> libs/yaml:begin(parser: p) | at c |> libs/yaml:kind(cur: c): k |> ...inspect c... |> libs/yaml:finish(cur: c) | eof pp |> libs/yaml:close(parser: pp) | err ep |> libs/yaml:close(parser: ep) | err e |> ... ───────────────────────────────────────────────────────────────────────── ⚠ DESIGN-DEBT / REVISIT (grep: DESIGN-DEBT yaml): this library is a bit wonky and does not yet play fully to Koru's strengths. * FIXED: the read-only accessors (`kind`, `scalar`, `error-text`) BORROW the handle (`<live>` / `<open>`) instead of consuming + re-minting it — the clean accessor form (cf. pcre2 `group.text { m: *Match<match> }`). * STILL WONKY: `begin`/`advance` return a MULTI-branch shape (`at` cursor | `eof`/`err` parser), so they re-mint the parser obligation on the non-cursor branches. There IS a real reason for that (each branch needs its own single obligation) — it is not ceremony. * THE FIX (deferred): add an OPTIONAL effect-branch parse form — a library-driven `walk` yielding each event via `! event e` (modelled on pcre2 `find.all ! match` / sqlite3 `! row`), keeping the manual primitives for fine control. That removes the re-minting entirely. ─────────────────────────────────────────────────────────────────────────

Phantom lifecycles

Derived from the phantom labels in the declarations below — state! issues an obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.

Parser 1 state open!
Cursor 1 state live!

open

<open!> index.kz:98

close

<!open> index.kz:129

error-text

<open> index.kz:139

begin

<!open><live!><open!> index.kz:153

advance

<!live><live!><open!> index.kz:184

finish

<!live> index.kz:210

kind

<live> index.kz:227

scalar

<live> index.kz:250