Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions examples/check-as-tool.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- check-as-tool.ilo — using `ilo check` from shell as a verifier tool.
--
-- `ilo check <file>` runs the verifier without executing. Exit 0 means
-- clean; any other code means diagnostics were emitted. Default output
-- is human-readable; pass `--json` for one JSON object per diagnostic on
-- stdout (newline-delimited) to consume programmatically.
--
-- ilo check broken.ilo -- human-readable, exit 1 on error
-- ilo check broken.ilo --json -- JSON-line diagnostics, exit 1
-- ilo check src/*.ilo -- check multiple files
-- ilo check --show-effects file.ilo -- emit inferred effect sets too
--
-- The JSON shape per line (companion to the ILO diagnostic registry):
-- { "code": "ILO-T005", "severity": "error",
-- "message": "undefined function 'foo'",
-- "labels": [{ "line": 7, "col": 12, "start": 142, "end": 145,
-- "message": "here", "primary": true }],
-- "notes": ["in function 'main'"],
-- "suggestion": "did you mean 'foo2'?" }
--
-- Use `ilo check --json` in a pre-commit hook to enforce zero diagnostics:
--
-- git diff --name-only --cached -- '*.ilo' \
-- | xargs -r ilo check --json \
-- | jq -e 'select(.severity == "error")' && exit 1
--
-- For CI gating, the same pattern with `select(.code | startswith("ILO-T"))`
-- catches all type-system errors while letting warnings pass.

-- This file itself is a trivial valid program — `ilo check check-as-tool.ilo`
-- should exit 0 and emit nothing. That's the contract the documentation
-- above relies on.

ok>n;42

-- run: ok
-- out: 42
33 changes: 33 additions & 0 deletions examples/discard-bind.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- discard-bind.ilo — `_=expr` (ILO-36) silences ILO-T033 when the result of
-- a side-effecting call is intentionally dropped.
--
-- ilo has functional semantics: `mset m k v` does NOT mutate `m`, it returns
-- a new map. So `_=mset m k v` is rarely useful (the side-effect-free shape
-- means the call is genuinely a no-op without the rebind).
--
-- The real use case is (b) from the CHANGELOG: calling a side-effecting
-- function at non-tail position where the return value is irrelevant.
-- `run2` is the canonical example — the spawn IS the side effect, the
-- RunResult is incidental when you only care that it ran.

-- Sequential spawns where we don't care about the output, only that they ran.
-- Without `_=`, each `run2` call would trip ILO-T033 "unused expression result".
ping-then-touch out:t>R t t
_=run2 "echo" ["pinged"]
_=run2 "true" []
r=run2!! "echo" [out]
~r.stdout

-- Counter: same shape, three discards then a meaningful tail.
-- Demonstrates `_=` mixing freely with regular bindings.
warm-then-echo>t
_=run2 "true" []
_=run2 "true" []
r=run2!! "echo" ["warm"]
r.stdout

-- engine-skip: jit
-- run: ping-then-touch "ok"
-- out: ok
-- run: warm-then-echo
-- out: warm
50 changes: 50 additions & 0 deletions examples/idxof-substring.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- idxof-substring.ilo — `idxof s sub > O n` (ILO-39).
--
-- Returns the first code-point index of `sub` in `s`, or nil if not found.
-- Index is in code-point units, NOT raw bytes (same convention as `at`).
-- Empty `sub` returns 0 (Python / JS semantics).
--
-- Replaces the verbose `flt`+`len` workaround scrapingbee-chain and
-- tui-client personas reached for when locating substrings.

-- Find present: returns the index wrapped in Optional.
find-comma>O n;idxof "key=value" "="

-- Find absent: returns nil.
find-missing>O n;idxof "key=value" "?"

-- Empty needle: returns 0 (convention from Python str.find / JS indexOf).
find-empty>O n;idxof "anything" ""

-- Empty haystack: returns nil (no codepoints to scan, no match possible).
find-empty-hay>O n;idxof "" "x"

-- Unicode-correct: emoji is one codepoint, so the index after it is the
-- codepoint count, not the byte count of the UTF-8 representation.
find-after-emoji>O n;idxof "🦀 rust" "rust"

-- The blessed split-on-first-occurrence idiom: idxof then slc.
-- Returns the value half of a `key=value` string; panics via `!` on missing `=`.
parse-value s:t>t
i=idxof! s "="
slc s (+ i 1) (len s)

-- The same shape for the key half.
parse-key s:t>t
i=idxof! s "="
slc s 0 i

-- run: find-comma
-- out: 3
-- run: find-missing
-- out: nil
-- run: find-empty
-- out: 0
-- run: find-empty-hay
-- out: nil
-- run: find-after-emoji
-- out: 2
-- run: parse-value "host=example.com"
-- out: example.com
-- run: parse-key "host=example.com"
-- out: host
38 changes: 38 additions & 0 deletions examples/pkg-semver-range.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- pkg-semver-range.ilo — `ilo add` semver constraint reference.
--
-- Companion to pkg-registry.ilo (which shows the runtime `use "owner/repo"`
-- shape). This file documents the install-time CLI: which version
-- constraints `ilo add` accepts, what they mean, and what ends up in
-- ilo.lock.
--
-- The constraints follow Cargo / npm semver semantics:
--
-- ilo add owner/repo -- latest stable
-- ilo add owner/repo@1.2.3 -- exact pin
-- ilo add owner/repo@^1.2.3 -- >= 1.2.3, < 2.0.0 (caret: API-compat)
-- ilo add owner/repo@~1.2.3 -- >= 1.2.3, < 1.3.0 (tilde: patch-only)
-- ilo add owner/repo@>=1.2,<2 -- explicit comma-separated range
-- ilo add owner/repo@1.x -- wildcard at any position
-- ilo add owner/repo@26.5 -- CalVer-style pin (treated as 26.5.x)
-- ilo add owner/repo --branch foo -- track a branch tip (no semver)
-- ilo add owner/repo --rev <sha> -- exact commit (no semver)
--
-- The constraint string is stored verbatim in `ilo.toml` under
-- [dependencies], and the resolved exact version goes into `ilo.lock`.
-- `ilo add` (no constraint) writes `^MAJOR.MINOR.PATCH` of the latest
-- stable; pass `--exact` to write `=MAJOR.MINOR.PATCH` instead.
--
-- For private-org installs, set IL_PKG_TOKEN to a fine-grained GitHub PAT
-- with `contents: read` on the source repo. The resolver picks it up
-- automatically; the constraint syntax is unchanged.
--
-- This file does not exercise the resolver (that would need a live
-- network call against the example registry). For the runtime side once
-- a package is installed, see pkg-registry.ilo. Both files together cover
-- the install-then-import flow end-to-end.

-- Marker fn so this file builds clean under examples_engines.
ok>n;42

-- run: ok
-- out: 42
34 changes: 34 additions & 0 deletions examples/run-bg-tail.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- run-bg-tail.ilo — `run-bg cmd argv > R n t` (ILO-35).
--
-- Fire-and-forget background spawn. Returns Ok(pid:n) immediately without
-- waiting for the child. Child inherits parent's stdout/stderr and reads
-- /dev/null on stdin. Use when you want to start a long-running server or
-- worker and continue executing ilo code.
--
-- `run-bg` differs from `run` (waits for completion) and `run2` (waits +
-- returns typed RunResult). Use run-bg for daemons, batch jobs, or any
-- process whose output you don't care about from inside ilo.

-- Spawn a short sleep in the background; we get the pid back immediately
-- and main returns without waiting for the sleep to finish.
spawn-pid>n;run-bg!! "sleep" ["0.05"]

-- pid is always positive on success.
pid-positive>b;p=run-bg!! "sleep" ["0.05"];>p 0

-- Spawn-then-continue: launch a no-op background process, then do real work.
-- The example doesn't need to coordinate with the child; that's the point.
spawn-then-compute>n
_=run-bg!! "true" []
+ 21 21

-- Nonexistent command returns Err (spawn failure is observable).
spawn-missing-is-err>b;r=run-bg "no-such-command-xyz" [];?r{~p:false;^e:true}

-- engine-skip: jit
-- run: pid-positive
-- out: true
-- run: spawn-then-compute
-- out: 42
-- run: spawn-missing-is-err
-- out: true
24 changes: 24 additions & 0 deletions examples/style/README.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- style/ — side-by-side examples showing the same behaviour two different
-- ways. The point is to bias agents toward the manifesto-preferred shape
-- by making the contrast visible at the example level.
--
-- Files in this directory each show:
-- - the dense / preferred form (token-minimal, principle 1)
-- - the explicit form (more familiar to readers from other languages)
-- and pin both with `-- run:` so the regression harness keeps them in
-- lockstep.
--
-- Index:
--
-- foreach-vs-map.ilo -- `@x xs{...}` vs `map (x:_>_;...) xs`
-- q-vs-match.ilo -- `?r{ok(v):...;err(e):...}` vs explicit ?
-- pipe-vs-nested.ilo -- threaded `use<-` vs nested calls
-- prefix-vs-infix.ilo -- `+x y` vs `x + y` for arithmetic
--
-- These exist as compile-only references; the harness exercises them via
-- their `-- run:` headers.

ok>n;0

-- run: ok
-- out: 0
23 changes: 23 additions & 0 deletions examples/style/foreach-vs-map.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- foreach-vs-map.ilo — `@x xs{...}` vs `map (x:_>_;...) xs`.
--
-- Both compute the same result. The `@x xs{...}` foreach form is preferred
-- because it costs fewer tokens AND reads more naturally as "for each x in
-- xs do ...". Use `map` when you need to build a list from a transformation;
-- use `@x` when you're folding into an accumulator or doing side effects.

-- Preferred: @x foreach with explicit accumulator. 4 tokens after the
-- function header.
sum-foreach xs:L n>n
total=0
@x xs{total=+total x}
total

-- Equivalent: build a list of zeros and reduce, or use fld directly. fld
-- is shorter (and the right choice when you don't need an accumulator
-- visible by name), but the foreach above generalises to side effects.
sum-fld xs:L n>n;fld (acc:n x:n>n;+acc x) xs 0

-- run: sum-foreach [1 2 3 4 5]
-- out: 15
-- run: sum-fld [1 2 3 4 5]
-- out: 15
28 changes: 28 additions & 0 deletions examples/style/prefix-vs-infix.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- prefix-vs-infix.ilo — `+x y` vs `x + y` for arithmetic.
--
-- ilo allows BOTH prefix and infix for the four arithmetic operators.
-- The prefix form is preferred because:
-- 1. It saves whitespace tokens.
-- 2. It composes uniformly: `sqrt + (* a a) (* b b)` reads left-to-right
-- with no precedence reasoning. The same shape with infix needs
-- careful grouping — `sqrt (* a a) + (* b b)` does NOT compute the
-- hypotenuse (sqrt applies only to the first argument, then `+` adds
-- the rest), so the infix attempt at hypot reads `sqrt(9) + 16 = 19`
-- instead of `sqrt(25) = 5`.
--
-- Prefer prefix. Use infix only at the top level of a single binary op
-- where there's no precedence ambiguity (`x = + a b` reads fine; mixing
-- infix and prefix in the same expression invites surprises).

-- Preferred: prefix throughout. Reads as `sqrt of (a*a + b*b)`.
hypot a:n b:n>n;sqrt + (* a a) (* b b)

-- The wrong-feeling infix shape, included so the gotcha is visible.
-- The example harness pins the wrong answer (19, not 5) so any future
-- change in associativity is caught here, not by personas in the wild.
hypot-broken a:n b:n>n;sqrt (* a a) + (* b b)

-- run: hypot 3 4
-- out: 5
-- run: hypot-broken 3 4
-- out: 19
73 changes: 73 additions & 0 deletions examples/webhook-verify.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
-- webhook-verify.ilo — Stripe-style HMAC-SHA256 webhook signature check.
--
-- Real webhook receivers need three things for security:
-- 1. Constant-time signature comparison (no early-exit timing leak)
-- 2. Replay-window enforcement (reject signatures older than N seconds)
-- 3. Canonical signed-payload format (timestamp + delimiter + body)
--
-- ilo provides all three primitives directly: hmac-sha256 for the digest,
-- ct-eq for constant-time text equality, and idxof+slc for parsing the
-- `t=<ts>,v1=<sig>` Stripe-Signature header shape.
--
-- This is the per-builtin reference for hmac-sha256 in a real use case.
-- For the raw builtin demo, see crypto-primitives.ilo.

-- Compute the expected signature for a Stripe-style signed payload.
-- Stripe's signed string is `<timestamp>.<body>` — both joined with `.`.
sign-payload secret:t ts:t body:t>t
signed=++ts "." body
hmac-sha256 secret signed

-- Parse `t=<ts>,v1=<sig>` -> Map{"t":<ts>, "v1":<sig>}.
-- Real headers can carry multiple v* schemes; this picks the first v1.
parse-header h:t>M t t
m=mmap
i-comma=idxof! h ","
ts-pair=slc h 0 i-comma
sig-pair=slc h (+ i-comma 1) (len h)
i-eq-ts=idxof! ts-pair "="
i-eq-sig=idxof! sig-pair "="
m=mset m "t" (slc ts-pair (+ i-eq-ts 1) (len ts-pair))
m=mset m "v1" (slc sig-pair (+ i-eq-sig 1) (len sig-pair))
m

-- Verify a signature with a freshness window. Returns true only when:
-- 1. The header parses cleanly
-- 2. The recomputed HMAC matches the header's v1 (ct-eq, not =)
-- 3. The signed timestamp is within `window-sec` of `nw`
-- Note: `nw` not `now` - `now` is a builtin and ILO-P011 rejects it as a name.
verify-webhook secret:t header:t body:t nw:n window-sec:n>b
m=parse-header header
ts-text=mget!! m "t"
v1=mget!! m "v1"
expected=sign-payload secret ts-text body
ts=num!! ts-text
age=- nw ts
sig-ok=ct-eq expected v1
fresh=<= age window-sec
& sig-ok fresh

-- ── Test vectors ─────────────────────────────────────────────────────────

-- Pre-computed signature for `secret`=`whsec_test`, `ts`=`1700000000`,
-- body=`{"id":42}`. Joined string is `1700000000.{"id":42}`.
ok-sig>b;verify-webhook "whsec_test" "t=1700000000,v1=0883b47833284e5d3b8fffddf4e143870ebbac24eb2540ca2f0a8325f297277a" "{\"id\":42}" 1700000060 300

-- Same payload but the body has been tampered with - signature mismatch.
bad-body>b;verify-webhook "whsec_test" "t=1700000000,v1=0883b47833284e5d3b8fffddf4e143870ebbac24eb2540ca2f0a8325f297277a" "{\"id\":99}" 1700000060 300

-- Same payload but `nw` is 10 minutes after the signed timestamp; outside
-- the 5-minute (300s) replay window.
stale>b;verify-webhook "whsec_test" "t=1700000000,v1=0883b47833284e5d3b8fffddf4e143870ebbac24eb2540ca2f0a8325f297277a" "{\"id\":42}" 1700000600 300

-- Show the recomputed signature so anyone tweaking the example can re-anchor.
echo-sig>t;sign-payload "whsec_test" "1700000000" "{\"id\":42}"

-- run: echo-sig
-- out: 0883b47833284e5d3b8fffddf4e143870ebbac24eb2540ca2f0a8325f297277a
-- run: ok-sig
-- out: true
-- run: bad-body
-- out: false
-- run: stale
-- out: false
3 changes: 3 additions & 0 deletions src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,9 @@ const BUILTINS: &[(&str, &[&str], &str)] = &[
("hex", &["t"], "t"),
("hex-rev", &["t"], "t"),
("ct-eq", &["t", "t"], "b"),
// idxof s sub > O n (ILO-39): first code-point index of `sub` in `s`,
// or nil. Tree-bridge eligible, no FnRef, no I/O.
("idxof", &["t", "t"], "O n"),
// Raw-bytes crypto (ILO-383). Both accept hex-encoded text, decode to bytes,
// and return hex-encoded SHA-256 digest. Error (ILO-R009) on odd-length or
// non-hex input.
Expand Down
Loading