From 09345520367f20f351beec0ea19b85d1fc9cfb42 Mon Sep 17 00:00:00 2001 From: Jeremy Howard Date: Thu, 12 Nov 2026 04:53:41 +1000 Subject: [PATCH] Build BPL expressions from Python and batch compatible cells --- DEV.md | 20 +- README.md | 16 +- TARGET.md | 16 +- cli/src/cli.rs | 2 +- cli/src/editor.rs | 2 +- cli/src/worker.rs | 9 +- cli/tests/cli.rs | 16 +- .../BPL.tmbundle/Syntaxes/bpl.tmLanguage.json | 8 +- .../BPL.tmbundle/Syntaxes/hpl.tmLanguage.json | 2 +- editors/jetbrains/BPL.xml | 30 +- editors/jetbrains/README.md | 2 +- editors/macos/BasedPL-de.txt | 10 +- editors/macos/BasedPL-es.txt | 11 +- editors/macos/BasedPL-fr.txt | 10 +- editors/macos/BasedPL-uk.txt | 10 +- editors/macos/BasedPL-us.txt | 10 +- editors/vim/syntax/bpl.vim | 8 +- editors/windows/README.md | 2 +- editors/windows/layout.h | 66 +- lib/README.md | 2 +- lib/array.bpl | 428 +- lib/dyalog.bpl | 1412 ++--- lib/graph.bpl | 360 +- lib/numeric.bpl | 346 +- lib/power.bpl | 58 +- lib/regex.bpl | 4 +- lib/string.bpl | 232 +- lib/tree.bpl | 572 +-- nbs/arrays.ipynb | 50 +- nbs/assignment.ipynb | 14 +- nbs/autodiff.qmd | 78 +- nbs/bpl.xml | 8 +- nbs/bracket-types.ipynb | 36 +- nbs/cli.qmd | 6 +- nbs/comparison.qmd | 142 +- nbs/composition.qmd | 38 +- nbs/data.ipynb | 10 +- nbs/dfns.ipynb | 66 +- nbs/distributions.ipynb | 6 +- nbs/evaluation.qmd | 76 +- nbs/fonts/BQN386B.woff2 | Bin 97432 -> 97472 bytes nbs/foundations.qmd | 4 +- nbs/gallery/attractor.bpl | 14 +- nbs/gallery/boids.bpl | 8 +- nbs/gallery/dragon.bpl | 8 +- nbs/gallery/flow.bpl | 12 +- nbs/gallery/mandelbrot.bpl | 16 +- nbs/gallery/mandelbrot32.bpl | 16 +- nbs/gallery/orbits.bpl | 8 +- nbs/gallery/phyllotaxis.bpl | 6 +- nbs/gallery/reaction.bpl | 16 +- nbs/getting-started.ipynb | 34 +- nbs/glyphs.qmd | 22 +- nbs/glyphs/alpha-underbar.qmd | 6 +- nbs/glyphs/alpha.qmd | 12 +- nbs/glyphs/at.qmd | 34 +- nbs/glyphs/backslash-bar.qmd | 6 +- nbs/glyphs/backslash.qmd | 6 +- nbs/glyphs/braces.qmd | 8 +- nbs/glyphs/brackets.qmd | 22 +- nbs/glyphs/circle-backslash.qmd | 50 +- nbs/glyphs/circle-bar.qmd | 6 +- nbs/glyphs/circle-diaeresis.qmd | 2 +- nbs/glyphs/circle-divide.qmd | 6 +- nbs/glyphs/circle-slash.qmd | 8 +- nbs/glyphs/circle-star.qmd | 30 +- nbs/glyphs/circle-stile.qmd | 2 +- nbs/glyphs/circle-times.qmd | 6 +- nbs/glyphs/colon.qmd | 26 +- nbs/glyphs/comma-bar.qmd | 6 +- nbs/glyphs/comma.qmd | 14 +- nbs/glyphs/del-diaeresis.qmd | 2 +- nbs/glyphs/del.qmd | 2 +- nbs/glyphs/delta-stile.qmd | 6 +- nbs/glyphs/diamond-underbar.qmd | 26 +- nbs/glyphs/diamond.qmd | 20 +- nbs/glyphs/domino.qmd | 16 +- nbs/glyphs/dot.qmd | 46 +- nbs/glyphs/down-arrow.qmd | 8 +- nbs/glyphs/down-shoe.qmd | 2 +- nbs/glyphs/down-tack.qmd | 6 +- nbs/glyphs/epsilon-underbar.qmd | 4 +- nbs/glyphs/epsilon.qmd | 4 +- nbs/glyphs/equal-underbar-slash.qmd | 2 +- nbs/glyphs/equal-underbar.qmd | 6 +- nbs/glyphs/equals.qmd | 2 +- nbs/glyphs/error-guard.qmd | 8 +- nbs/glyphs/hash.qmd | 8 +- nbs/glyphs/hydrant.qmd | 8 +- nbs/glyphs/inverted-question.qmd | 8 +- nbs/glyphs/iota.qmd | 62 +- nbs/glyphs/jot-diaeresis.qmd | 16 +- nbs/glyphs/lamp.qmd | 2 +- nbs/glyphs/left-arrow.qmd | 32 +- nbs/glyphs/left-shoe-underbar.qmd | 6 +- nbs/glyphs/left-shoe.qmd | 12 +- nbs/glyphs/left-tail-arrow.qmd | 2 +- nbs/glyphs/less-than.qmd | 2 +- nbs/glyphs/omega-underbar.qmd | 4 +- nbs/glyphs/omega.qmd | 8 +- nbs/glyphs/parentheses.qmd | 12 +- nbs/glyphs/partial.qmd | 26 +- nbs/glyphs/quad-colon.qmd | 52 +- nbs/glyphs/quad-diamond.qmd | 6 +- nbs/glyphs/quad-equal.qmd | 2 +- nbs/glyphs/quad-jot.qmd | 8 +- nbs/glyphs/quad.qmd | 2 +- nbs/glyphs/question.qmd | 8 +- nbs/glyphs/quote.qmd | 6 +- nbs/glyphs/radical.qmd | 2 +- nbs/glyphs/rho.qmd | 26 +- nbs/glyphs/right-arrow-from-bar.qmd | 2 +- nbs/glyphs/right-left-arrows.qmd | 40 +- nbs/glyphs/right-shoe.qmd | 12 +- nbs/glyphs/slash-bar.qmd | 4 +- nbs/glyphs/slash.qmd | 12 +- nbs/glyphs/squad.qmd | 64 +- nbs/glyphs/star-diaeresis.qmd | 16 +- nbs/glyphs/star.qmd | 4 +- nbs/glyphs/stile-tilde.qmd | 6 +- nbs/glyphs/superscript-minus.qmd | 2 +- nbs/glyphs/thorn.qmd | 4 +- nbs/glyphs/tilde.qmd | 2 +- nbs/glyphs/tricolon.qmd | 2 +- nbs/glyphs/up-arrow.qmd | 6 +- nbs/glyphs/up-down-arrow.qmd | 42 +- nbs/glyphs/up-shoe.qmd | 2 +- nbs/glyphs/up-tack.qmd | 2 +- nbs/glyphs/vee.qmd | 2 +- nbs/glyphs/zilde.qmd | 8 +- nbs/hpl.xml | 2 +- nbs/index-notation.qmd | 60 +- nbs/index.ipynb | 20 +- nbs/indexing.ipynb | 62 +- nbs/introspection.ipynb | 30 +- nbs/keyboard.qmd | 8 +- nbs/keyed.ipynb | 152 +- nbs/llms.txt | 42 +- nbs/magics.ipynb | 1072 +--- nbs/mathish.qmd | 2 +- nbs/modules.qmd | 18 +- nbs/notebooks.ipynb | 1029 +--- nbs/numbers.qmd | 78 +- nbs/overview.ipynb | 18 +- nbs/performance.qmd | 28 +- nbs/playground/page.js | 2 +- nbs/plot.ipynb | 34 +- nbs/principles.ipynb | 48 +- nbs/processes.qmd | 6 +- nbs/python.ipynb | 969 +++- nbs/regex.ipynb | 14 +- nbs/repl.qmd | 22 +- nbs/scripts.ipynb | 6 +- nbs/strings.ipynb | 16 +- nbs/study/01_basics.ipynb | 120 +- nbs/study/02_functions.ipynb | 64 +- nbs/study/03_operators.ipynb | 88 +- nbs/study/04_nested-arrays.ipynb | 62 +- nbs/study/05_searching-indexing.ipynb | 58 +- nbs/study/06_rearranging-arrays.ipynb | 32 +- nbs/study/07_numerical-tools.ipynb | 6 +- nbs/style.qmd | 12 +- nbs/syntax.qmd | 74 +- nbs/system/constants.qmd | 6 +- nbs/system/decompose.qmd | 24 +- nbs/system/detach.qmd | 10 +- nbs/system/edge.qmd | 4 +- nbs/system/explain.qmd | 26 +- nbs/system/image.qmd | 2 +- nbs/system/index.qmd | 26 +- nbs/system/literal.qmd | 4 +- nbs/system/memory.qmd | 6 +- nbs/system/mime.qmd | 6 +- nbs/system/path.qmd | 2 +- nbs/system/prefs.qmd | 44 +- nbs/system/r.qmd | 2 +- nbs/system/rand.qmd | 2 +- nbs/system/signal.qmd | 4 +- nbs/system/special.qmd | 6 +- nbs/system/storage.qmd | 4 +- nbs/system/time.qmd | 2 +- nbs/terms.qmd | 12 +- nbs/web.ipynb | 28 +- nbs/xml.ipynb | 38 +- py/src/lib.rs | 39 +- pyproject.toml | 2 +- python/basedpl/__init__.py | 9 +- python/basedpl/assets/fonts/BQN386B.ttf | Bin 273588 -> 273768 bytes python/basedpl/assets/fonts/SAX2B.ttf | Bin 106576 -> 106616 bytes .../Contents/Resources/BasedPL-de.keylayout | 66 +- .../Contents/Resources/BasedPL-es.keylayout | 68 +- .../Contents/Resources/BasedPL-fr.keylayout | 66 +- .../Contents/Resources/BasedPL-uk.keylayout | 66 +- .../Contents/Resources/BasedPL-us.keylayout | 66 +- python/basedpl/bpl.tmLanguage.json | 8 +- python/basedpl/bpltests.py | 24 +- python/basedpl/browser/SAX2B.woff2 | Bin 24848 -> 24904 bytes python/basedpl/browser/language.js | 4 +- python/basedpl/browser/layout.json | 22 +- python/basedpl/browser/lb.js | 2 +- python/basedpl/expressions.py | 12 +- python/basedpl/font.py | 12 +- python/basedpl/functions.py | 12 +- python/basedpl/hpl.tmLanguage.json | 2 +- python/basedpl/keyboards.py | 9 +- src/array.rs | 7 +- src/array/storage.rs | 6 +- src/array/text.rs | 18 +- src/batch.rs | 34 +- src/derivative.rs | 8 +- src/derivative/tape.rs | 15 +- src/display.rs | 2 +- src/distribution.rs | 10 +- src/element.rs | 6 +- src/error.rs | 6 +- src/eval.rs | 43 +- src/eval/assign.rs | 22 +- src/eval/binder.rs | 106 +- src/eval/bindings.rs | 11 +- src/eval/operators.rs | 18 +- src/eval/operators/agenda.rs | 5 +- src/eval/operators/axes.rs | 4 +- src/eval/operators/each.rs | 5 +- src/eval/operators/folds.rs | 11 +- src/eval/operators/key.rs | 16 +- src/eval/operators/power.rs | 2 +- src/eval/operators/products.rs | 7 +- src/eval/operators/rank.rs | 4 +- src/eval/rewrite.rs | 66 +- src/eval/session.rs | 27 +- src/events.rs | 21 +- src/explain.rs | 2 +- src/fusion.rs | 29 +- src/keyed.rs | 2 +- src/name.rs | 2 +- src/pervasive.rs | 18 +- src/pervasive/fold.rs | 2 +- src/plan.rs | 35 +- src/primitive.rs | 6 +- src/primitive/facts.rs | 16 +- src/primitive/indexing.rs | 6 +- src/primitive/search.rs | 4 +- src/primitive/structural.rs | 8 +- src/program.rs | 24 +- src/program/blocks.rs | 109 +- src/program/blocks/grouping.rs | 4 +- src/program/blocks/items.rs | 4 +- src/program/blocks/record.rs | 5 +- src/program/contraction.rs | 10 +- src/program/layout.rs | 6 +- src/program/run.rs | 29 +- src/program/trace.rs | 117 +- src/search.rs | 2 +- src/symbols.rs | 14 +- src/syntax.rs | 140 +- src/system.rs | 2 +- src/transform.rs | 12 +- testkit/src/reference.rs | 5 +- testkit/src/reference/runner.rs | 56 +- testkit/src/timing.rs | 23 +- tests/acceptance/held_out.bpl | 110 +- tests/core.rs | 210 +- tests/derivatives.rs | 172 +- tests/explain.rs | 28 +- tests/highlighting.mjs | 4 +- tests/input.mjs | 6 + tests/jit.rs | 8 +- tests/ratchet.rs | 10 +- tests/reference.rs | 36 +- tests/reference/README.md | 6 +- tests/reference/aplcart.bpl | 4570 ++++++++--------- tests/reference/aplcart_mathish.bpl | 4 +- tests/reference/april.bpl | 2634 +++++----- tests/reference/april_mathish.bpl | 2 +- tests/reference/bqn.bpl | 110 +- tests/reference/core.bpl | 4340 ++++++++-------- tests/reference/core_mathish.bpl | 46 +- tests/reference/distributions.bpl | 84 +- tests/reference/dyalog.bpl | 192 +- tests/reference/dyalog_mathish.bpl | 2 +- tests/reference/graphics.bpl | 52 +- tests/reference/lib.bpl | 90 +- tests/reference/ngn.bpl | 732 +-- tests/reference/regex.bpl | 4 +- tests/reference/static-int32.bpl | 8 +- tests/reference/static.bpl | 170 +- tests/reference/support/april.bpl | 2 +- tests/reference/support/avl.bpl | 2 +- tests/reference/support/redblack.bpl | 2 +- tests/reference/support/sbst.bpl | 2 +- tests/reference/support/splay.bpl | 6 +- tests/settings.rs | 112 +- tests/speed.rs | 11 +- tests/speed/arrays.bpl | 130 +- tests/speed/bodies.bpl | 4 +- tests/speed/calls.bpl | 10 +- tests/speed/cells.bpl | 6 +- tests/speed/cohorts.bpl | 4 +- tests/speed/comparisons_mathish.bpl | 6 +- tests/speed/growth.bpl | 16 +- tests/speed/images.bpl | 12 +- tests/speed/keyed.bpl | 10 +- tests/speed/large_cells.bpl | 6 +- tests/speed/maps.bpl | 160 +- tests/speed/models.bpl | 124 +- tests/speed/moving.bpl | 4 +- tests/speed/packing.bpl | 4 +- tests/speed/steps.bpl | 20 +- tests/speed/structure.bpl | 58 +- tests/speed/tables.bpl | 24 +- tests/test_api.py | 88 +- tests/test_basic.py | 40 +- tests/test_bpltests.py | 8 +- tests/test_expressions.py | 21 +- tests/test_graphics.py | 22 +- tests/test_install.py | 10 + tests/test_ipython.py | 2 +- tests/test_kernel.py | 44 +- tests/test_keylayout.py | 16 +- tests/test_repl.py | 4 +- tests/test_worker.py | 22 +- tests/wasm.mjs | 34 +- wasm/README.md | 2 +- wasm/src/lib.rs | 4 +- 324 files changed, 12613 insertions(+), 13562 deletions(-) diff --git a/DEV.md b/DEV.md index 0943589b..efa20662 100644 --- a/DEV.md +++ b/DEV.md @@ -66,7 +66,7 @@ Accuracy: `nbs/numbers.qmd`'s "Accuracy": floats within 4 ulps of correctly roun Non-commitments don't license breakage: keep the exact results the Numbers page lists. Remove code existing only for agreement between results or accuracy past documented bounds, where removal cuts lines or adds speed without substantive complexity and breaks nothing else. -Dynamic kinds: autodiff and fast code target static kinds, branching and updating with approaches like Agenda (`⍚`), Under (`f@g`, `f@[p]`) or selective assignment (`x.[p]←`), not with dfn guards on traced values. Dynamic kinds serve interactive analysis: tacit code over dataset-sized arrays, and simple dfns over values such as dates and strings. A call settles when its kernel can't fail an item at its operands' ranges (`plan::ranges`: a held constant's own value, nonnegative where the operand is known never negative, otherwise any), as rustymath's `Kernel::may_raise_on` decides; that check is the fact's one home. Only settled calls defer and fuse. Integer arithmetic settles: it computes at the narrowest width that holds every result (u8+u8 → i16) and wraps at i64. `√ * ⍟ ○` settle only where the ranges meet the domain. A call that doesn't settle runs eagerly as one whole-array call and never defers; on a complex result, the whole call runs again, complex. The `ₐ` forms are the fast path for real-domain math. Dfn guards run correctly; they have no throughput, instruction or allocation budgets. Data-dependent guards stop batching. The speed tests keep dynamic-kinds cases, so costs stay known and new slowdowns fail. A slow event from a value-dependent type under dynamic kinds: `allows` (known cost). +Dynamic kinds: autodiff and fast code target static kinds, branching and updating with approaches like Agenda (`⍚`), Under (`f@g`, `f@[p]`) or selective assignment (`x.[p]←`), not with dfn guards on traced values. Dynamic kinds serve interactive analysis: tacit code over dataset-sized arrays, and simple dfns over values such as dates and strings. A call settles when its kernel can't fail an item at its operands' ranges (`plan::ranges`: a held constant's own value, nonnegative where the operand is known never negative, otherwise any), as rustymath's `Kernel::may_raise_on` decides; that check is the fact's one home. Only settled calls defer and fuse. Integer arithmetic settles: it computes at the narrowest width that holds every result (u8+u8 → i16) and wraps at i64. `√ * ⊛ ○` settle only where the ranges meet the domain. A call that doesn't settle runs eagerly as one whole-array call and never defers; on a complex result, the whole call runs again, complex. The `ₐ` forms are the fast path for real-domain math. Dfn guards run correctly; they have no throughput, instruction or allocation budgets. Data-dependent guards stop batching. The speed tests keep dynamic-kinds cases, so costs stay known and new slowdowns fail. A slow event from a value-dependent type under dynamic kinds: `allows` (known cost). Build and test: @@ -99,8 +99,8 @@ Timings use the installed build, or a kept build passed through `build=`. Instruction counts: code layout alone moves timings more than a few percent, hiding per-call changes in copies; instruction counts don't move with load or layout. The speed tests count every speed case (below); `instructions(cases)` counts any expression in fresh processes. Both count loops as real runs make them. -- A case is an expression (`k(⌈/⊢)⌸a`) that each step evaluates at top level and forces in full, as the speed suite's timings do (`timing::time`), through the worker's `steps` request. A case naming `N` makes its own calls (`{⍵}⍣N 0`: one dfn call a step) and must make one per step: a map like `f¨N⍴0` calls its dfn once on all cells, giving no per-call cost. -- Fresh single-threaded `bpl` processes at four `N`, three each (`runs=`), lowest count per `N`; each successive pair's slope, Δinstructions/ΔN, cancels startup and `setup=`; the median of the three slopes drops a one-off cost starting past some size (`≢•ucs N⍴"ab" "cde"`: about 5×10⁶ more from about 131,350 items). All `N` lie past warm-up, from rough counts at 1 step and, for a step under 10⁸ instructions, at enough steps to retire about 10⁸: allocating cases retire up to about 5×10⁶ extra, varying by process, while the allocator settles; successive `N` 5×10⁸ instructions apart keep that under 1% of a slope. `counts=` sets two or more `N`. Reads `/usr/bin/time -l` (macOS), `perf stat` (Linux). +- A case is an expression (`k(⌈/⊢)⌸a`) that each step evaluates at top level and forces in full, as the speed suite's timings do (`timing::time`), through the worker's `steps` request. A case naming `N` makes its own calls (`{ω}⍣N 0`: one dfn call a step) and must make one per step: a map like `f¨Nρ0` calls its dfn once on all cells, giving no per-call cost. +- Fresh single-threaded `bpl` processes at four `N`, three each (`runs=`), lowest count per `N`; each successive pair's slope, Δinstructions/ΔN, cancels startup and `setup=`; the median of the three slopes drops a one-off cost starting past some size (`≢•ucs Nρ"ab" "cde"`: about 5×10⁶ more from about 131,350 items). All `N` lie past warm-up, from rough counts at 1 step and, for a step under 10⁸ instructions, at enough steps to retire about 10⁸: allocating cases retire up to about 5×10⁶ extra, varying by process, while the allocator settles; successive `N` 5×10⁸ instructions apart keep that under 1% of a slope. `counts=` sets two or more `N`. Reads `/usr/bin/time -l` (macOS), `perf stat` (Linux). - `build=` another build; a list (`build=[base, None]`) counts each at the same `N`, side by side, with the first-to-last change. - Counts run `bpl --worker` with a `steps` request. Builds without that request cannot be counted; compare them with timings. - Speed-case counts: a speed case's footer gives, after its time budgets, its instructions and allocated bytes for each item on each platform: `⍝⍝: 2.5 3.4 4.6 instructions macos 20.2 linux 33.1 allocated macos 8 linux 8`. Time budgets of `-` make a count-only case, as in `speed/growth.bpl` and `speed/steps.bpl`. `timing::counts` runs each case on one thread with compiling off, once to warm and once counted, or `n` times in a `steps` section, and divides by the setup's `n`. It takes no timing lock. On `BUDGET_CPU`, a count fails above 1.25× its footer value (`rustymath_timing::COUNT_MARGIN`), or 1.1× in `calls` and `steps` sections, and allocated bytes get 1 byte an item more. Footers hold measurements: after a change that moves counts, or a compiler update, run the speed tests with `BASEDPL_COSTS=1` and write the counts with `set_counts(log)`. Counts check the uncompiled path that first runs and the browser take; timings check compiled stages. @@ -185,7 +185,7 @@ For an uncompiled slowdown, measure the affected existing cases with `compile="n A high copy count does not by itself show extra memory passes. Compare raw ns/item and a one-read/one-write control at nearby sizes in the same execution mode. Addition touches three arrays; a negating copy touches two. They can cross cache tiers at different sizes. [The addition diagnosis](meta/elementwise-add-cost.md) records a controlled example. -JIT kernels exist only while a process runs. `BASEDPL_JIT_DISASM` set → `bpl` prints each kernel it compiles to stderr: a description line, then machine code. `bpl-tests jit_disasm CASE --setup SETUP --prefs PREFS` runs a case so: `bpl-tests jit_disasm '+/ux>127ₓ₁' --setup 'ux←(¿(2*22)⍴256)ₓ₁' --prefs '•prefs ["kinds":"static"]'`. A stage compiles on its first run only where that run repays compiling (`blocks::Blocks::repays`): its items × the passes its parts save × `SAVED_NS` exceed its parts' compile cost, which is higher for a fold or a comparison. +JIT kernels exist only while a process runs. `BASEDPL_JIT_DISASM` set → `bpl` prints each kernel it compiles to stderr: a description line, then machine code. `bpl-tests jit_disasm CASE --setup SETUP --prefs PREFS` runs a case so: `bpl-tests jit_disasm '+/ux>127ₓ₁' --setup 'ux←(¿(2*22)ρ256)ₓ₁' --prefs '•prefs ["kinds":"static"]'`. A stage compiles on its first run only where that run repays compiling (`blocks::Blocks::repays`): its items × the passes its parts save × `SAVED_NS` exceed its parts' compile cost, which is higher for a fold or a comparison. `--x86`: the x86-64 code CI and Linux run. First builds basedpl's library and its linked rustymath library for Linux x86-64 into `/tmp/aai-ws-x86`: minutes after a change → only for an x86-code question. Unfinished edit stops basedpl compiling → reads rustymath's library alone, saying so first. Linux: macOS builds omit rustymath's native linear algebra kernels. Libraries only: the Python extension crates can't build for Linux on this Mac. Their `release` profile matches CI's `test` profile in x86-64 flags and optimization. The build sets `CC_x86_64_unknown_linux_gnu=clang` itself; don't move it into `.cargo/config.toml` (CI's Linux release builds read that file in a manylinux container without `clang`). Don't use `--emit asm`: it splits code into other codegen units and inlines differently. @@ -322,11 +322,11 @@ The lexer/parser records byte spans and structural syntax without evaluating exp `symbols.rs` owns APL, simplified and traditional Hanzi spellings. Its rows also hold each glyph's names: a glyph name for the character, which is also its page's file name in `nbs/glyphs/`, and a name for each valence, or one name where both valences do the same thing. The glyph reference, named entry and help read them there. Python's exported names read them, with each system function's name, through `symbols::builtins`. `hanzi_aliases` enables Hanzi glyph spellings, canonicalizing token decisions while retaining original source and spans. Other Hanzi remain name characters. `_` quotes the next non-whitespace character into a name, including a glyph alias. The `notation` preference affects structured function/operator display, not stored source, serialization or structural APIs; help and editor metadata use the same spelling table. -The binder resolves runtime categories right to left. It owns unfinished strands, trains and bound arguments; shared function nodes are immutable. Grammatical reduction produces application requests instead of calling functions. An array that holds a function as an item has its own binder category, `Callable` (`Category::bound`), while `Category::of`, which assignment's grammar reads, keeps it `Value`. `Binder::push` works out each entity's role once, as it pushes it: `Category::before` resolves `Callable` against the role of the entity below, which is to its right, to `Function` where that entity is a subject or an absent `⍺`, and to `Value` otherwise. `Rule::get` and the strengths don't change. A function-only operand slot converts such an array to `FunctionNode::Array` in `constant_operand`, and `Function::from_value` makes the same conversion where a call by text needs a function. Calls give values: a call's result, a subscript's selection and a scope's result bind as arrays, whatever they hold. `Bound::given` is the rule's one home: it makes a function result a function atom and gives the `Bound` the category `Value`, which `Bound::category` returns in place of the value's own. `Entity::held` puts a `Bound` on the binder's stack with that category. A field read from a name is the one exception: `Entity::grouped` gives that group its field's binding back, as `Session::static_category` reads it. Dot access is written as a `⊃` call (`assign::dot_path`), so `("k"⊃T) x` calls the field too. Primitive/operator rows own names, operand kinds, ranks, pervasion, axis behaviour and reduction identities. Change those rows instead of duplicating metadata in consumers. A non-extending primitive above its natural ranks runs through Rank; pervasive Each/Outer/Rank paths reuse whole-array kernels. +The binder resolves runtime categories right to left. It owns unfinished strands, trains and bound arguments; shared function nodes are immutable. Grammatical reduction produces application requests instead of calling functions. An array that holds a function as an item has its own binder category, `Callable` (`Category::bound`), while `Category::of`, which assignment's grammar reads, keeps it `Value`. `Binder::push` works out each entity's role once, as it pushes it: `Category::before` resolves `Callable` against the role of the entity below, which is to its right, to `Function` where that entity is a subject or an absent `α`, and to `Value` otherwise. `Rule::get` and the strengths don't change. A function-only operand slot converts such an array to `FunctionNode::Array` in `constant_operand`, and `Function::from_value` makes the same conversion where a call by text needs a function. Calls give values: a call's result, a subscript's selection and a scope's result bind as arrays, whatever they hold. `Bound::given` is the rule's one home: it makes a function result a function atom and gives the `Bound` the category `Value`, which `Bound::category` returns in place of the value's own. `Entity::held` puts a `Bound` on the binder's stack with that category. A field read from a name is the one exception: `Entity::grouped` gives that group its field's binding back, as `Session::static_category` reads it. Dot access is written as a `⊃` call (`assign::dot_path`), so `("k"⊃T) x` calls the field too. Primitive/operator rows own names, operand kinds, ranks, pervasion, axis behaviour and reduction identities. Change those rows instead of duplicating metadata in consumers. A non-extending primitive above its natural ranks runs through Rank; pervasive Each/Outer/Rank paths reuse whole-array kernels. Dot names rewrite to Pick and dot brackets to selection. Numeric subscripts derive selection on arrays and `⍠` on functions from the bound operand. Writable subscripts normalize to selection calls at assignment. `ⱼ`, `ₑ` or `ᵣ` between two arrays then rewrites to the primitives it stands for, as `xⱼy` to `x+y×0ⱼ1` (`syntax::joined_term`). Dot parentheses evaluate a namespace: `T.(expr)` and `T⍎text` bind each key on the first axis of `T` to its item or major cell as a local, resolve other names in the caller and keep assignments local. They share the scoped execution route and lexical lifetime checks. Between functions, dot remains inner product. -`Bindings` (`eval/bindings.rs`) holds every binding: the frames of running calls and scopes, each module's names, the current frame and the running module. Other code reads and writes bindings only through its methods. A lookup gives where a name is bound, as a `Place`, with its binding, and reads, writes and removals take a `Place`. `Bindings::here` gives the place where a plain assignment binds a name. Parsing interns each name as a `Name` (`name.rs`), a number that is the same in every session, and a name becomes text again only where text comes in or goes out. Each frame has slots on one stack that every frame shares: first one for each of the six names that a call binds itself, then one for each of a dfn's locals, the names before an `←` in its statements (`Definition::locals`). A call allocates nothing for its names. A call with no left argument binds `⍺` to an absent argument, so lookup finds the call's own `⍺` and never an outer frame's. A name without a slot, such as one that `⍎` binds or an assignment in a scope binds, goes in its frame's side map. A scope's frame holds its record, and a lookup that the side map misses there reads a key through the record's own index of its keys (`Keys::position`), so a scope copies nothing and a key's text never becomes a `Name`. Each module maps interned names directly to bindings. A module `Place` retains its name across deletion and reinsertion. Lexical frames use non-owning parent indices. Closures see live bindings, not snapshots. A dfn's last read of a local, `⍵` or `⍺` takes the binding out of its slot (`syntax::release`), and leaves `Binding::Released`, which stops lookup there with a VALUE error. The binder hands a dfn call the arguments it owns (`Argument`), so `⍵` and `⍺` hold them without a copy. `dispatch` gives a dfn the arguments as its caller gave them, and so a composition or fork with an operand that can use them (`Function::owns`, set when the function is built); it lends them to any other composition or fork. An operator hands its operand each value that it made and doesn't read again: `∘`'s inner result, a fork's tine results, `⍣`'s states, a fold's running result, and the cells and selections that it gathers. It gives its own arguments to its operand's last call, as `∘`, `⊘`, `↣`, `↢`, a unit `⍚`, `⍣` with one count and a fork's left tine do. It lends them where it reads them after a call, as Under, Rank, Axis, stencils, Over, inner products and `⍣`'s other forms do, and it lends any value that it reads again, such as the state that `⍣∞` compares and the batches that a batched try reruns after a stop. A train reads its arguments in its last part and in each fork's left tine, and the last of those reads takes them. Plain assignment is local; modified/selective assignment updates the nearest existing binding. Retain the resolved place and original array across modifier calls rather than looking the name up again. Returns and outer writes reject functions, including those nested in arrays/prototypes, whose frames would not survive. Tail calls retain dependencies from arguments and the called function. No collector is used; recheck this lifetime reasoning before permitting escaping lexical captures. +`Bindings` (`eval/bindings.rs`) holds every binding: the frames of running calls and scopes, each module's names, the current frame and the running module. Other code reads and writes bindings only through its methods. A lookup gives where a name is bound, as a `Place`, with its binding, and reads, writes and removals take a `Place`. `Bindings::here` gives the place where a plain assignment binds a name. Parsing interns each name as a `Name` (`name.rs`), a number that is the same in every session, and a name becomes text again only where text comes in or goes out. Each frame has slots on one stack that every frame shares: first one for each of the six names that a call binds itself, then one for each of a dfn's locals, the names before an `←` in its statements (`Definition::locals`). A call allocates nothing for its names. A call with no left argument binds `α` to an absent argument, so lookup finds the call's own `α` and never an outer frame's. A name without a slot, such as one that `⍎` binds or an assignment in a scope binds, goes in its frame's side map. A scope's frame holds its record, and a lookup that the side map misses there reads a key through the record's own index of its keys (`Keys::position`), so a scope copies nothing and a key's text never becomes a `Name`. Each module maps interned names directly to bindings. A module `Place` retains its name across deletion and reinsertion. Lexical frames use non-owning parent indices. Closures see live bindings, not snapshots. A dfn's last read of a local, `ω` or `α` takes the binding out of its slot (`syntax::release`), and leaves `Binding::Released`, which stops lookup there with a VALUE error. The binder hands a dfn call the arguments it owns (`Argument`), so `ω` and `α` hold them without a copy. `dispatch` gives a dfn the arguments as its caller gave them, and so a composition or fork with an operand that can use them (`Function::owns`, set when the function is built); it lends them to any other composition or fork. An operator hands its operand each value that it made and doesn't read again: `∘`'s inner result, a fork's tine results, `⍣`'s states, a fold's running result, and the cells and selections that it gathers. It gives its own arguments to its operand's last call, as `∘`, `⊘`, `↣`, `↢`, a unit `⍚`, `⍣` with one count and a fork's left tine do. It lends them where it reads them after a call, as Under, Rank, Axis, stencils, Over, inner products and `⍣`'s other forms do, and it lends any value that it reads again, such as the state that `⍣∞` compares and the batches that a batched try reruns after a stop. A train reads its arguments in its last part and in each fork's left tine, and the last of those reads takes them. Plain assignment is local; modified/selective assignment updates the nearest existing binding. Retain the resolved place and original array across modifier calls rather than looking the name up again. Returns and outer writes reject functions, including those nested in arrays/prototypes, whose frames would not survive. Tail calls retain dependencies from arguments and the called function. No collector is used; recheck this lifetime reasoning before permitting escaping lexical captures. Module 0 owns session names. Each `•load` runs in a new module, without the caller's frame and with echo off, and returns its non-private names alphabetically. Modules remain for the session's lifetime. Recursive loading of a canonical path is a DOMAIN error. Paths starting `./` or `../` resolve against the source file; other paths use the working directory. Execute/load do not run the top-level preference header. @@ -378,7 +378,7 @@ Rank tries `whole` before `each_pair`. `whole`, in order: 4. `pervasive_cells`: a pervasive primitive under nested Rank; 5. monadic operand: `cell_rule`, which also takes folds, atops and a bound left argument. -- `primitive_cells` runs a primitive on the whole argument along the cells' axes. A form that `extends` runs whole at a cell rank at least its own. Other forms follow the `cells` fact (`CellRule`) in their `Primitive::info()` row: `⊖ , ↑ ↓ ⌷ #` and rotate-first call their axis forms; `⍉` sends each cell's axes after the frame's; `≢`, `⍴` give constants; a reshape keeping a cell's items in order is a view; `⊂` encloses each cell of rank 1 or more via its axis form, whose cells are views of the argument's items. Any other form with a left argument every cell shares takes `paired_cells`. +- `primitive_cells` runs a primitive on the whole argument along the cells' axes. A form that `extends` runs whole at a cell rank at least its own. Other forms follow the `cells` fact (`CellRule`) in their `Primitive::info()` row: `⊖ , ↑ ↓ ⌷ #` and rotate-first call their axis forms; `⍉` sends each cell's axes after the frame's; `≢`, `ρ` give constants; a reshape keeping a cell's items in order is a view; `⊂` encloses each cell of rank 1 or more via its axis form, whose cells are views of the argument's items. Any other form with a left argument every cell shares takes `paired_cells`. - `paired_cells`: joins each pair of cells for `,` and `⍪`; calls a form that extends once where both cells are at least its ranks; first repeats an argument every cell shares over the frame (`agreement::spread`). - `padded_call`, `paired_cells` leave an argument with a named frame axis to the per-cell path, which pairs frames by name (`named_frame`). Names on cell axes pair in whole calls as per-cell calls pair them. - A pervasive primitive folds or scans a simple array along the cell's axis (`folded_cells`); `f/∘,` folds all cell axes at once. An atop applies its inner function's rule, then Rank for its outer function. @@ -390,7 +390,7 @@ Inverse nodes propagate fixed-argument sides through composition. `f⁻←g`, `f Form rewriter (`eval/rewrite.rs`): the one home of fast combinations. -- Rule: a row of BPL text, a pattern over `⍵` and `⍺`, beside the hidden `Special` that computes it (`⍟1+⍵` beside `Log1p`); `rules` adds the selections, one per order and pick. A rule's functions must be glyphs and its constants literals: a name holding `1` or `⍟` isn't recognised. +- Rule: a row of BPL text, a pattern over `ω` and `α`, beside the hidden `Special` that computes it (`⊛1+ω` beside `Log1p`); `rules` adds the selections, one per order and pick. A rule's functions must be glyphs and its constants literals: a name holding `1` or `⊛` isn't recognised. - The rewriter reads a function and an expression as one tree of calls (`Form`), replacing each subtree a rule matches with a call of the rule's function. `Function::new` rewrites a function's monadic and dyadic forms once, keeping the results (`rewritten`); `dispatch` calls them in the function's place; inverses, Under and display read the original node. `Binder::reduced` rewrites each list of nodes after `joined` and `members_with`. Every spelling, expressions included, takes the combination from its first call. `calls_tines` builds a train whose tines `may_combine` accepts; `Function::new` then rewrites it. - `combined` runs the structural replacements (selections, ranks, Key's counts and folds) before any running level. An algorithm unsuited to the arguments, or a running level carrying one → `combined` calls the functions the combination replaces, which the level then sees. - Each combination has a row under "Fast combinations" on its glyph pages. `tests/explain.rs` checks via `•explain` that every documented spelling of a structural combination takes it; `core.bpl` reference cases check each gives what its functions give in turn, and its route cases require each combination's route. The coverage matrix runs each rule whose function maps its argument (`mapping_patterns`), respelling any form's own case a rule would capture (`rewrites`). @@ -436,7 +436,7 @@ Settings: once per process in `settings.rs`, read from there, not passed through - Classification of 1,024 items or more first groups items sharing one array, before reading any (`search::shared_classes`). Imports make repeated texts share: `keyed::Texts` makes each distinct text once per `•csv` column, `•json` document and Python conversion, up to 65,536 distinct texts. - Exact integer, Boolean and character storage uses keys; float searches use their width. Integer haystacks take the whole-number shortcut only for exactly whole float needles. Tolerant indexing validates whole-key matches unless the haystack is exactly whole, falling back to buckets on mismatch. Classification chains tolerance through sorted runs by design: it needn't match every member to one representative. Other cells: exact hashes or first-number buckets with `Value::matches` confirmation; pairwise comparison where indexing is unsuitable. Grade is stable. - Position buffers: u32, becoming integer storage without another pass. `⍷` and monadic Key take each class's positions from `search::class_positions`, which keeps a class's own buffer for 32-bit positions and copies narrower ones once from the shared buffer. Dyadic Key selects rows through `Groups::each` slices. Key's group-fold idiom folds through `search::numbered`'s class numbers; where `fold_groups` declines, Key builds positions from those numbers. Key with operand `≢⊢` (also built by `≢∘⊢`) counts classes via `search::class_counts`, building no positions. -- Under Rank and in batched maps, `⍳`, `∊` and `⍸` whose haystack differs per cell search each cell's own haystack in one call on the whole arguments (`primitive::searched_cells`). +- Under Rank and in batched maps, `ι`, `∊` and `⍸` whose haystack differs per cell search each cell's own haystack in one call on the whole arguments (`primitive::searched_cells`). `Domain`, `with_field!`: select working reals, Complex64 or BigRational for generic numeric algorithms; compact storage selects the domain without scanning items. `number::read` converts once; `Field::array` builds compact float or complex results without a `Number` per item. Each caller lists the domains it supports. Compact complex input doesn't take a real path merely because all imaginary parts are zero; results built via `Value::complex` can normalize to float storage. @@ -484,7 +484,7 @@ Fusion (`fusion.rs`; its module doc owns eligibility rules and limits: don't cop - Rules: reverse mode's `⌈`/`⌊` fold: the tangent times a mask of each lane's chosen item, summed; its transpose reads. Mix of a dual with a nested primal mixes primal and tangent apart. `fold` and scans with `+ × ⌈ ⌊`, `+.×` and outer products have rules; kernels reading numbers directly leave active arguments to general paths. Folds by primitives, products of primitives, `⌹`, `⊥` and stacks of matrices route through `transform::call` as operations (`primitive::Operation`): their rules (`derivative::solve`, `decode`, `product_dual`, `determinant`, `fold_dual`) run on a `&Context`, calling `Operation::call` for needed products, folds and inverses. Rank's whole-argument routes send carried stacks through `Pairing::Matrices` → `⌹⍤2`, `+.×⍤2`, `-.×⍤2` take stacked rules in one call, `batch` on or off. Attached rules, system functions, and folds and products by non-primitives take the same `transform::call`, as `transform::Op`'s other variants. - `⌹`, `-.×` keep the glyph's own result as primal → same values with or without `∂`. Tangents and transposes multiply by `R`: the inverse `numeric::matrix_inverse` takes from one factorization via rustymath's `Stacked::inverse`, or exact row reduction for exact matrices; `regular_inverse` gives UNSUPPORTED where rustymath's regularity test finds the matrix singular. Only `B` active: `R` is `⌹A`, pseudoinverse included. - One number in `x`: `f∂ x` runs `f` once with `x` seeded. More: `linearized` pushes a record level just below its `∂` call's level, runs `f` once with each part of `x` seeded with a record at it (`linear.rs`): a tangent whose items are never computed, which the same rules extend into a graph of linear calls. Newest level a record level → records the linear map its form's facts name (`linear::call`). `reverse` reads the Jacobian from the record: a transpose per result item or a forward evaluation per number of `x`, whichever fewer. -- An `f∂←g` JVP rule is called with `⍺` a dual of zeros whose tangent is the argument's → in reverse mode it extends the record (`derivative::attached`). Such a function called dyadically: DOMAIN ERROR wherever `∂` needs a derivative through it (`derivative::two_arguments`). +- An `f∂←g` JVP rule is called with `α` a dual of zeros whose tangent is the argument's → in reverse mode it extends the record (`derivative::attached`). Such a function called dyadically: DOMAIN ERROR wherever `∂` needs a derivative through it (`derivative::two_arguments`). - Code reading a dual array's storage gets dual atoms (correct, per-item speed). A rule packs an array holding its level's duals, at any depth, into one dual before splitting it (`derivative::packed`). Readers outside a call use `transform.rs`'s read point: `Value::observed` (counts, positions, ranks, selectors, `≡`, guard predicates) reads a dual's primal; `Items`' integer readers read carried items through it (`Value::observed_number`). Its `Result`'s error, `transform::Stop`, names the level owning a batched run. A stop is `ErrorKind::Stop`: no guard catches it, even one naming it; catch-all handlers let it pass (`ErrorKind::is_control`). Display reads via `Value::displayed` (never inside a batched run). `Value::computed` (kernels, via `primitive::numeric`) gives UNSUPPORTED for a dual atom. - Writes to bindings outside a `∂` call are recorded and stripped when it returns. diff --git a/README.md b/README.md index 7f040428..89736ae2 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,11 @@ avg 2 4 9 5 -Use `bpl -e 'avg←+/÷≢ ⋄ avg 2 4 9'` for a shell command. The [command-line guide](https://answerdotai.github.io/basedpl/cli.html) covers source files and pipes. +Use `bpl -e 'avg←+/÷≢ ◇ avg 2 4 9'` for a shell command. The [command-line guide](https://answerdotai.github.io/basedpl/cli.html) covers source files and pipes. ## Interactive use -In the REPL, type a backtick followed by a glyph name. For example, `` `iota `` becomes `⍳` when you press Tab or type a non-letter. Abbreviations and Alt-key shortcuts are available. `]help +` shows help for `+`. [`•prefs`](https://answerdotai.github.io/basedpl/system/prefs.html) changes display settings, such as boxes and how much of a large array shows. See [REPL](https://answerdotai.github.io/basedpl/repl.html) and the [glyph reference](https://answerdotai.github.io/basedpl/glyphs.html), which lists each glyph’s key. +In the REPL, type a backtick followed by a glyph name. For example, `` `iota `` becomes `ι` when you press Tab or type a non-letter. Abbreviations and Alt-key shortcuts are available. `]help +` shows help for `+`. [`•prefs`](https://answerdotai.github.io/basedpl/system/prefs.html) changes display settings, such as boxes and how much of a large array shows. See [REPL](https://answerdotai.github.io/basedpl/repl.html) and the [glyph reference](https://answerdotai.github.io/basedpl/glyphs.html), which lists each glyph’s key. In Jupyter, select the installed **BasedPL** kernel. Cells share definitions and support completion, Shift-Tab help and interruption. You can also use `%%bpl` cells in a Python notebook. See [Using BPL notebooks](https://answerdotai.github.io/basedpl/notebooks.html). @@ -55,10 +55,10 @@ Numbers separated by spaces form a vector. Arithmetic applies to every item: 11 12 13 -Operators modify or combine functions. Reduce (`/`) turns addition into summation. `⍳10` generates 0…9: +Operators modify or combine functions. Reduce (`/`) turns addition into summation. `ι10` generates 0…9: ``` bpl -+/⍳10 ++/ι10 ``` 45 @@ -134,14 +134,14 @@ Axes can have names, and positions along them can have string keys. Describe the ``` bpl axes←["city":"NY" "LA";"month":"Jan" "Feb" "Mar"] -sales←axes:[10 20 30⋄40 50 60] +sales←axes:[10 20 30◇40 50 60] "LA" "Feb"⌷sales +/⍠"month" sales ``` 50 - ["city":2]⍴["NY":60 "LA":150] + ["city":2]ρ["NY":60 "LA":150] Keys and names travel with axes through operations such as transpose. Arithmetic aligns matching names and keys. See [Axis keys](https://answerdotai.github.io/basedpl/keyed.html). @@ -150,7 +150,7 @@ Keys and names travel with axes through operations such as transpose. Arithmetic A vector of counts keeps the history: one state for each count, where count 0 is the initial value. Here, double up to four times: ``` bpl -2×⍣(⍳5) 1 +2×⍣(ι5) 1 ``` 1 2 4 8 16 @@ -178,7 +178,7 @@ Explore [iteration and inverses](https://answerdotai.github.io/basedpl/glyphs/st [Polynomials](https://answerdotai.github.io/basedpl/glyphs/quad-jot.html) support coefficients, roots and evaluation. Polynomial functions can be [differentiated](https://answerdotai.github.io/basedpl/glyphs/partial.html): for f(x) = 1 + 2x + 3x², f′(2) = 14. ``` bpl -f←[1 2 3]ₓ⌻ ⋄ f∂2ₓ +f←[1 2 3]ₓ⌻ ◇ f∂2ₓ ``` 14 diff --git a/TARGET.md b/TARGET.md index 55b10daa..3c0538d4 100644 --- a/TARGET.md +++ b/TARGET.md @@ -15,7 +15,7 @@ Python APIs are not fixed constraints. If preserving Python semantics causes non - Results agree only within the documented accuracy (4 ulps), across spellings, routes, sizes and threads. - "The bar" (below) sets the criteria; it changes only with Jeremy's OK. Done: "Acceptance". - `fib` or similar recursive functions do not need to be fast at all -- Value-dependent dynamic `√`, `*`, `⍟` and `○` do not need fast, fused or recorded execution. Their explicit approximate forms do. The boundary is a required numeric-value read, not a glyph whitelist; kinds or proofs can already make a dynamic call fast. +- Value-dependent dynamic `√`, `*`, `⊛` and `○` do not need fast, fused or recorded execution. Their explicit approximate forms do. The boundary is a required numeric-value read, not a glyph whitelist; kinds or proofs can already make a dynamic call fast. - Ordinary integer arithmetic, including floor `÷`, stays native. Unbounded exact arithmetic requires the `ᵣ` domain and need not be fast. ## Non-goals (this release) @@ -99,12 +99,12 @@ call with declared roles → parameter observation → semantic forms (part 5) Each: job; callers; not (what must not exist). `meta/foundation-reset.md`'s "Deletion checklist" lists the named old code still in the tree. 1. Values (`array.rs`, `array/storage.rs`). `Value`: atom | stored array (`ArrayData`: compact, on rustymath buffers and views, or mixed) | pending (`Value::Pending`) | carried (a derivative or batch level's value, `Value::Carried`). Mixed arrays hold nested values, records and functions; a nested item may be pending (a pytree holding a tracer); numeric leaves enter the graph. Uniform results assemble into compact arrays with more axes; ragged ones stay nested; native segmented storage (entries/shapes/leaf/prototype) is deferred. Native kinds: Boolean, fixed and ordinary integers, f16/f32/f64, complex, characters. `ᵣ` values, mixed kinds, functions and records are host values: a host boundary borrows the actual value's nonnumeric structure, never inventing a numeric description. Mixed-array traversal, copying, prototypes, comparison, display and Python/CSV/JSON conversion stay. Every host read of a pending node's items passes `Node::into_storage` (part 10's chokepoint): it refuses unbound linear reads and judges others by input roles; `ArrayData::storage` reads stored arrays, not trace inputs. A partial read is a `Copy` node; the scheduler computes only what it reads (tinygrad's shrink pushed into a kernel). Leaf buffers are host-owned; external and GPU memory: part 23. Not: segmented storage or five-part trees; packing per producer; items cached twice; host reads past the chokepoint; carriers in stored data; level code in storage. -2. Descriptions (`program::Desc`; JAX abstract eval): native kind, atom flag, layout, range; nonnumeric structure stays in Trees or the host value. The row factory derives a step's description from its origin in 1 kind/shape/range pass over operand descriptions and parameter values, before choosing the step; `Desc::leaf` describes a leaf once, on entry. Kinds: Boolean; fixed integer of a width; ordinary machine integer of a width; float of 16, 32 or 64 bits; complex of either width; character; weak or strong. Kinds and their join: 1 home, `number::Kind` and `number::Kind::join`; rustymath's canonical machine-dtype promotion stays shared; `array/storage.rs`'s 1 stored-result joining policy serves assembly and descriptions through that join. Integral weak+fixed joins use the common dtype, retaining the fixed domain. Only an existing keyed ordinary weak literal contributes its minimum representable integer dtype through `Kind::literal`; opaque inputs retain their declared kind bounds. Computation and result consume the same projected kinds. Literal projection in containers applies only to a fixed-domain result; ordinary static strands retain their ordinary width. No value-fit admission or new provenance flag. Static kinds join results computed apart (writes, compacting selects, map assembly, `⍣` histories) to 1 compact kind, as JAX gives `vmap`, `where` and `scan` outputs 1 dtype; strands, rows and dynamic kinds keep each item's kind. Wider accumulators come from seeds, conversions and the ordinary-integer width policy, never an accumulator field. Ordinary integers use machine arithmetic: dynamic kinds narrow where intrinsic kinds and in-graph ranges prove a width suffices, else use i64 and wrap, never inspecting opaque input extrema; static ordinary and explicit fixed integers wrap at their width. Integer `÷` floors, as `÷ₓ` does; float arguments keep float division and mixed promotion; `÷ₐ`, `÷ᵣ` and the unbounded `ᵣ` domain: part 21. Dynamic kinds keep value-dependent kinds (`√ * ⍟ ○` on reals can give complex): where ranges don't decide one, the call reads the operands it depends on (part 5's roles), inside and outside traces (part 10). Part 21's primitive real forms and `÷ₓ` (floor division to a machine integer) resolve before the unmarked kind rule and reads, so never depend on values. Under dynamic kinds, real-float `⌊ ⌈` give ordinary integers saturating at the 64-bit range: positive infinity gives the maximum, negative infinity the minimum, and NaN gives 0. Static unmarked real-float `⌊ ⌈` retain floats; explicit `⌊ₓ ⌈ₓ` create machine integers at the selected width in both modes. Exact-domain and Gaussian complex rounding keep their domains. Monadic `×` of floats gives an ordinary integer under dynamic kinds. Keyed and named layouts are graph values: keyed agreement is a node whose maps and length come from the keys at run time, where a key missing on one side reads as the fill (selecting a missing key: INDEX error, part 20); a cache key compares key sets by identity before contents; a layout's renderer travels in descriptions. Cacheable renderers are canonical data-free callables with no run-time bindings (stateless native ones, the image renderer included); one needing data or bindings renders normally but declines recording and graph caching, through the same scheduler. Ranges (DuckDB statistics) come from each object's interval rule over operand ranges. Opaque inputs (forced graphs, recordings, recorded bodies) have intrinsic kind bounds; narrower carried ranges are dropped, even proved ones. Literals keep their values in the key; in-graph operation guarantees and shape/count bounds at actual lengths stay, through unrolled iteration too. Ranges keep `√` and `⍟` real without reading items, choose narrower machine work, drop a gather's bounds check where its indices lie inside the axis, and prove an object's repair predicate false (part 6) through that predicate, never another threshold or admission policy. Possibly wrapped integer results get conservative ranges, never the unbounded interval: the minimum signed integer's magnitude may wrap negative, so it can't prove nonnegativity. A read's kind and range cover every numbered source it may select, its fill included (an invalid static read's fallback where validity isn't proved), from descriptions alone. A fold's range uses its actual item count or a conservative bound for the plan's accepted lengths. Refill re-derives and validates count-dependent facts (part 10). Not: a segmented description; opaque-input range boxes or numeric contracts on opaque input values; tracking assumed narrow input ranges; range-failure/retrace counters or invalidation on changed input extrema; automatic ordinary-integer overflow to BigInt or integer division to rational; a description from a value's `layout()` in a trace; parallel description helpers; a 2nd shape rule. +2. Descriptions (`program::Desc`; JAX abstract eval): native kind, atom flag, layout, range; nonnumeric structure stays in Trees or the host value. The row factory derives a step's description from its origin in 1 kind/shape/range pass over operand descriptions and parameter values, before choosing the step; `Desc::leaf` describes a leaf once, on entry. Kinds: Boolean; fixed integer of a width; ordinary machine integer of a width; float of 16, 32 or 64 bits; complex of either width; character; weak or strong. Kinds and their join: 1 home, `number::Kind` and `number::Kind::join`; rustymath's canonical machine-dtype promotion stays shared; `array/storage.rs`'s 1 stored-result joining policy serves assembly and descriptions through that join. Integral weak+fixed joins use the common dtype, retaining the fixed domain. Only an existing keyed ordinary weak literal contributes its minimum representable integer dtype through `Kind::literal`; opaque inputs retain their declared kind bounds. Computation and result consume the same projected kinds. Literal projection in containers applies only to a fixed-domain result; ordinary static strands retain their ordinary width. No value-fit admission or new provenance flag. Static kinds join results computed apart (writes, compacting selects, map assembly, `⍣` histories) to 1 compact kind, as JAX gives `vmap`, `where` and `scan` outputs 1 dtype; strands, rows and dynamic kinds keep each item's kind. Wider accumulators come from seeds, conversions and the ordinary-integer width policy, never an accumulator field. Ordinary integers use machine arithmetic: dynamic kinds narrow where intrinsic kinds and in-graph ranges prove a width suffices, else use i64 and wrap, never inspecting opaque input extrema; static ordinary and explicit fixed integers wrap at their width. Integer `÷` floors, as `÷ₓ` does; float arguments keep float division and mixed promotion; `÷ₐ`, `÷ᵣ` and the unbounded `ᵣ` domain: part 21. Dynamic kinds keep value-dependent kinds (`√ * ⊛ ○` on reals can give complex): where ranges don't decide one, the call reads the operands it depends on (part 5's roles), inside and outside traces (part 10). Part 21's primitive real forms and `÷ₓ` (floor division to a machine integer) resolve before the unmarked kind rule and reads, so never depend on values. Under dynamic kinds, real-float `⌊ ⌈` give ordinary integers saturating at the 64-bit range: positive infinity gives the maximum, negative infinity the minimum, and NaN gives 0. Static unmarked real-float `⌊ ⌈` retain floats; explicit `⌊ₓ ⌈ₓ` create machine integers at the selected width in both modes. Exact-domain and Gaussian complex rounding keep their domains. Monadic `×` of floats gives an ordinary integer under dynamic kinds. Keyed and named layouts are graph values: keyed agreement is a node whose maps and length come from the keys at run time, where a key missing on one side reads as the fill (selecting a missing key: INDEX error, part 20); a cache key compares key sets by identity before contents; a layout's renderer travels in descriptions. Cacheable renderers are canonical data-free callables with no run-time bindings (stateless native ones, the image renderer included); one needing data or bindings renders normally but declines recording and graph caching, through the same scheduler. Ranges (DuckDB statistics) come from each object's interval rule over operand ranges. Opaque inputs (forced graphs, recordings, recorded bodies) have intrinsic kind bounds; narrower carried ranges are dropped, even proved ones. Literals keep their values in the key; in-graph operation guarantees and shape/count bounds at actual lengths stay, through unrolled iteration too. Ranges keep `√` and `⊛` real without reading items, choose narrower machine work, drop a gather's bounds check where its indices lie inside the axis, and prove an object's repair predicate false (part 6) through that predicate, never another threshold or admission policy. Possibly wrapped integer results get conservative ranges, never the unbounded interval: the minimum signed integer's magnitude may wrap negative, so it can't prove nonnegativity. A read's kind and range cover every numbered source it may select, its fill included (an invalid static read's fallback where validity isn't proved), from descriptions alone. A fold's range uses its actual item count or a conservative bound for the plan's accepted lengths. Refill re-derives and validates count-dependent facts (part 10). Not: a segmented description; opaque-input range boxes or numeric contracts on opaque input values; tracking assumed narrow input ranges; range-failure/retrace counters or invalidation on changed input extrema; automatic ordinary-integer overflow to BigInt or integer division to rational; a description from a value's `layout()` in a trace; parallel description helpers; a 2nd shape rule. 3. Graph (`graph.rs`; tinygrad `UOp`). `graph::Node`: a step and its origin's operand list, each read through a rustymath `Mapping`, with a description per native output and its logical output tree; or a leaf: an input (key position, role and a free value's name; no step) or a computed buffer. Nodes, leaves included, are interned by part 4's structural origin facts (`keeping`). Forced → `result`: a read replaces the node with its result and drops its step and operands (tinygrad's `realize`), so later graphs read a leaf and no rule matches across it. `Live::of` walks a read's live nodes, hashes its key and gathers its inputs; `Program::of` converts them only on a cache miss; the scheduler, rules, recordings and `verify` read only `Program`. A view is a `Copy` node composed into its readers' maps, stored (1 copy) only for a reader whose kernel can't read through a map, as 1 capability test (`reads_through`) decides; readers of a shared pending copy compose their own reads without storing it. Rustymath's single numbered-source read planner composes copy chains unmaterialized: `Mapping::composed` for single-source affine maps, `Steps::through` for composition by inner axes beyond them; fill routes stay reads of scalar sources (part 4). Not: node builders outside dispatch; scheduling the live graph directly. -4. Steps (`program::Step`): the IR. `Map Convert Copy Gather Fold Product At Picks Join Call Draw Effect Index` (`Index`: an affine index step), each with its normal runner and glyph; the row factory derives a step's description and reads at construction and on refill, rule outputs and lowered steps included. Operands are the origin's list, named by position; no variant holds another list. The compiler's constant producer marks true source constants as literal nodes (`Value::constant`), including constant arrays, strings and records; functions keep their binding category. Names, captured data and formal arguments are registered inputs. Containers hold their items unread, including literal nodes. Host code forwarding an operand forwards that held value rather than reading and reconstructing it. An actual host item read is an observation boundary and returns a stored value. Data-independent constants computed by host code enter through the same literal constructor. A shape-derived value retains its count/refill dependency or structural observation guard; a current length is never frozen merely because it came from a description. The origin keeps: the operation; function values; operands as reads in call order; all captured free data and operator data, snapshotted and flattened at the call with `Trees` (part 10); roles; mode (static or dynamic kinds, Mathish: bits, not a parameter read); argument trees for flattened calls; the call's source site, which internal calls preserve and cached execution binds anew. Host operations key the source base that relative paths need, never diagnostic ranges or source text; numeric keys omit sites. Numeric values a kernel reads, such as tolerance, are operands with parameter roles named by the row's settings; host Power convergence reads its captured numeric scope, with no Loop setting or data protocol. Callable origins capture the numeric defaults that interpret their deferred bodies; a scoped activation uses those setting inputs and the origin's mode and never changes process defaults, which only an explicit preferences effect updates, with active contexts, through the same settings home. Pending nodes keep their call's settings. A route is a step, never a builder; a new job takes an existing step where one serves. Batching and JVP rules belong to rows; host iteration uses ordinary transformed body calls, not a loop rule; transposes are part 15's pass, keyed on linear facts. Index steps: `⍳` and `•index` are affine generators, never stored; `+ -`, negation and `×` by a constant keep them affine. Selection plans strides, wrapped and padded reads from coefficients (`Stride`, `Rotated`, `Padded`); clamped and reflected reads are `Padded` maps with `•edge` modes, not gathers; `Table::affine` stays for index arrays that are data. A chosen numerical fill is scalar data, described by kind and range without reading it; an options record gives its structure as parameters and its fill field as a data input; settings, counts, ranks and tolerances stay parameters. `Copy` (base and optional fill) and `Join` take numbered sources through rustymath's shared `ReadPlan` (part 9). `Join` finds positions by running the moving primitive on ids and serves strands, `,`, `⍪`, compaction, Under writes and joins' transposes; read-back distributes cotangents to every source through part 15's one mechanism. `Call` runs a planned primitive outside block stages (sort, grade, search, `⌹`) on forced operands, in its own stage. `Effect` reads the preceding effect's token and its own operands; a stage ends at it. +4. Steps (`program::Step`): the IR. `Map Convert Copy Gather Fold Product At Picks Join Call Draw Effect Index` (`Index`: an affine index step), each with its normal runner and glyph; the row factory derives a step's description and reads at construction and on refill, rule outputs and lowered steps included. Operands are the origin's list, named by position; no variant holds another list. The compiler's constant producer marks true source constants as literal nodes (`Value::constant`), including constant arrays, strings and records; functions keep their binding category. Names, captured data and formal arguments are registered inputs. Containers hold their items unread, including literal nodes. Host code forwarding an operand forwards that held value rather than reading and reconstructing it. An actual host item read is an observation boundary and returns a stored value. Data-independent constants computed by host code enter through the same literal constructor. A shape-derived value retains its count/refill dependency or structural observation guard; a current length is never frozen merely because it came from a description. The origin keeps: the operation; function values; operands as reads in call order; all captured free data and operator data, snapshotted and flattened at the call with `Trees` (part 10); roles; mode (static or dynamic kinds, Mathish: bits, not a parameter read); argument trees for flattened calls; the call's source site, which internal calls preserve and cached execution binds anew. Host operations key the source base that relative paths need, never diagnostic ranges or source text; numeric keys omit sites. Numeric values a kernel reads, such as tolerance, are operands with parameter roles named by the row's settings; host Power convergence reads its captured numeric scope, with no Loop setting or data protocol. Callable origins capture the numeric defaults that interpret their deferred bodies; a scoped activation uses those setting inputs and the origin's mode and never changes process defaults, which only an explicit preferences effect updates, with active contexts, through the same settings home. Pending nodes keep their call's settings. A route is a step, never a builder; a new job takes an existing step where one serves. Batching and JVP rules belong to rows; host iteration uses ordinary transformed body calls, not a loop rule; transposes are part 15's pass, keyed on linear facts. Index steps: `ι` and `•index` are affine generators, never stored; `+ -`, negation and `×` by a constant keep them affine. Selection plans strides, wrapped and padded reads from coefficients (`Stride`, `Rotated`, `Padded`); clamped and reflected reads are `Padded` maps with `•edge` modes, not gathers; `Table::affine` stays for index arrays that are data. A chosen numerical fill is scalar data, described by kind and range without reading it; an options record gives its structure as parameters and its fill field as a data input; settings, counts, ranks and tolerances stay parameters. `Copy` (base and optional fill) and `Join` take numbered sources through rustymath's shared `ReadPlan` (part 9). `Join` finds positions by running the moving primitive on ids and serves strands, `,`, `⍪`, compaction, Under writes and joins' transposes; read-back distributes cotangents to every source through part 15's one mechanism. `Call` runs a planned primitive outside block stages (sort, grade, search, `⌹`) on forced operands, in its own stage. `Effect` reads the preceding effect's token and its own operands; a stage ends at it. 5. Dispatch (`Operation::dispatch(Origin)`; JAX `Primitive.bind`, tinygrad's lazy graph): 1 numerical entry for primitives, folds/products, selects, writes, notation/joins, conversions, draws and effects; engine consumers call it, never private builders. In order: semantic forms resolve (row-defined forms and marks call their constituent operations through dispatch; host iteration expands actual function calls, part 13; Select normalizes into ordinary calls and reads, part 14); callable, data and description-only uses are classified before callable captures are traversed (containing a function isn't calling it); captures of executable callables and operator data join the ordered call-time inputs; then the newest level's rule handles the call through the same entry. `Operation::roles` names data and call-time parameter, Count, kind and structural reads before they occur. Higher-order consumers use part 19's shared typed-body query before selecting native Fold, Product or marked forms. Already-declared primitive operations retain the constant-time base case. 1 borrowed CallView lends the origin, bound functions, resolved role values and descriptions. 1 row factory computes kind, shape, range and geometry once, chooses the Step (or the normal host Call) and derives its reads from those results; that combined result serves live construction and refill alike; `fusion::built` constructs the pending node. A host-kind operation uses the normal Call stage; unknown result structure is an explicit host/recording boundary, never a generic pending-header protocol. A partial pending read is an ordinary Copy; a fold to an atom stays pending. Not: a second eager/traced numerical route; per-call force policies; builders or executors in consumers; speed rewrites at dispatch; metadata references captured as callees; special pending-atom readers instead of roles; independent describe/plan/maps callbacks recomputing the same geometry; an extra planner cache. 6. Rows (`primitive/facts.rs`) and rustymath objects (`facts.rs`, `recipe.rs`, `structure.rs`, `derivative.rs`). Row: its object; plan (kind and shape rules over known lengths); batching (`cells`: operation, wholes and frame count in, a dispatch call out; semantic host iteration uses the same body-call rules); inverse; Under form (structural, or computational through the inverse); linear facts (linear operands, jointly or each, and zero-tangent calls: JAX's `deflinear`, `defbilinear`); identity (its object's `identity` where one exists; structural functions such as `,` hold their own); cost; row facts (`passes`, `alternates`, `first_axis`); structural validity. No transpose per row (part 15). Object, each fact once: common formula; optional, separately named repair predicate and repair routine; partials; ranges; numeric validity conditions; fold properties. The repair predicate is an Ops formula giving 1 Boolean per item, shared by native and JIT execution; it owns width-dependent limits (trig's large-reduction limits: 2^23 for f64, 2^20 for f32). `Object::test` stays the Boolean-result formula for comparisons, not this predicate. Native SIMD computes the common formula, then repairs flagged items with the object's scalar routine before consumers use them; a proved-false predicate (part 2) needs no test or branch. Checked numeric validity is an Ops Boolean predicate paired with its error kind, shared by native and JIT execution; no predicate means total. The builder derives a step's potential to record a data error from these facts, its mode, descriptions and range proofs; unknown means potentially raising; Select case planning combines this property over its steps, with no separate validity evaluator. Shared formulas over `Ops` are the default for elementwise arithmetic; a substantial algorithm can instead be 1 object-owned `Call` through the same builder and runner, its common computation never duplicated in a formula and a legacy kernel. Native and compiled kernels instantiate the same width-specific formula over their `Ops` (recipe). The numeric Kernel interface keeps every supported machine kind for each operation, independent of block/JIT admission: Boolean, fixed integers, ordinary machine integers, f16/f32/f64 (f16 works in f32), native complex. Host exact arithmetic stays outside this machine interface, using the same canonical mathematical facts, not the legacy kernel engine. "Numerical algorithms" specifies the shared bodies. Defined forms call other primitives through dispatch (part 5). Not: kind-specific rule files; parallel facts tables (recipe bodies); name checks replacing facts; duplicated common scalar math beside a formula. -7. Rewrite engine (`program/rules.rs`; tinygrad `PatternMatcher`). 1 table of BPL pattern rows and structural rules (fold of a copy, contraction, sliding fold, copy commute). A rule: a root key (data: step kind, root primitive) that rejects; 1 match giving bindings; 1 builder from them. Rules run in the scheduler's pipeline on `Program`s after `∂` and batching (XLA's simplifier after JAX's transforms), a lone node's read included (part 8): the hidden specials they introduce (partial sorts, ranks, `log1p`, `expm1`) need only plans and kernels, and a pattern also matches its primitives' batched forms, which carry an axis. Every pending value has its origin, so a rule matches in every spelling: `⍟1+x` → `log1p` from 1 row, across statements, bindings, dfns and trains; it doesn't match across a read of its intermediate, which replaced that node with its result (part 3). A rule may change last bits, never leave the documented accuracy bounds (kmeans' cancelling rule is out: decision 14); each has a model-independent reason and a reference case. The table: selections of a grade or sort (`k↑⍋⍵`, `↑⍋⍵`, `i⌷⍋⍵`, `i⊃⍋⍵`, for `⍋ ⍒ < >`) as partial sorts; `⍋⍋⍵`, `⍋⍒⍵` as ranks; `⍟1+⍵` as `log1p` and `¯1+*⍵` as `expm1` (accuracy); a `+` fold of `×` whose operands read through affine maps as 1 product (index notation's level 3); a fold of a copy that repeats items as a fold of its source; `J+.×v` as a JVP and `u+.×J` as a VJP, where `J` is a Jacobian `∂` built and nothing has computed. Not rules: mandatory marked-form semantics (parts 5, 21); a combination of 1 call matched by its function's structure, which is its operator's own algorithm (monadic `-.×`, `+.×`: `primitive_expansion`); Key and Partition, which keep the released grouping algorithms and normal batching (part 12; a native segmented group representation is deferred). Not: a test written twice (a pre-check beside the match); a rule tried from 2 places; 1 rule in 2 directions written as 2 rules. +7. Rewrite engine (`program/rules.rs`; tinygrad `PatternMatcher`). 1 table of BPL pattern rows and structural rules (fold of a copy, contraction, sliding fold, copy commute). A rule: a root key (data: step kind, root primitive) that rejects; 1 match giving bindings; 1 builder from them. Rules run in the scheduler's pipeline on `Program`s after `∂` and batching (XLA's simplifier after JAX's transforms), a lone node's read included (part 8): the hidden specials they introduce (partial sorts, ranks, `log1p`, `expm1`) need only plans and kernels, and a pattern also matches its primitives' batched forms, which carry an axis. Every pending value has its origin, so a rule matches in every spelling: `⊛1+x` → `log1p` from 1 row, across statements, bindings, dfns and trains; it doesn't match across a read of its intermediate, which replaced that node with its result (part 3). A rule may change last bits, never leave the documented accuracy bounds (kmeans' cancelling rule is out: decision 14); each has a model-independent reason and a reference case. The table: selections of a grade or sort (`k↑⍋ω`, `↑⍋ω`, `i⌷⍋ω`, `i⊃⍋ω`, for `⍋ ⍒ < >`) as partial sorts; `⍋⍋ω`, `⍋⍒ω` as ranks; `⊛1+ω` as `log1p` and `¯1+*ω` as `expm1` (accuracy); a `+` fold of `×` whose operands read through affine maps as 1 product (index notation's level 3); a fold of a copy that repeats items as a fold of its source; `J+.×v` as a JVP and `u+.×J` as a VJP, where `J` is a Jacobian `∂` built and nothing has computed. Not rules: mandatory marked-form semantics (parts 5, 21); a combination of 1 call matched by its function's structure, which is its operator's own algorithm (monadic `-.×`, `+.×`: `primitive_expansion`); Key and Partition, which keep the released grouping algorithms and normal batching (part 12; a native segmented group representation is deferred). Not: a test written twice (a pre-check beside the match); a rule tried from 2 places; 1 rule in 2 directions written as 2 rules. 8. Scheduler (forcing: `graph.rs`, `program.rs`, `program/run.rs`). 1 pipeline, `schedule(&Program, &Running, from, runs) -> Plan`, then run preparation and execution, serves forcing on a miss, a trace's end and replay planning for the rest after `Ran::Rest`: `from` is 0 or the rest's first node; the 1 `Running` state supplies bound inputs, callable/template bindings and computed prefix values, never reconstructing initial-only bindings; `runs` (expected reuse) feeds the compile decision. Scheduling gathers, rewrites, orders, fixes stage ends (tinygrad's `indexing.py`) and outputs, plans legal cheap-chain recomputation and folds sharing stages; the cached plan holds this structure and kernel recipes. Run preparation makes cheap actual-count choices within the plan, outside item loops: compilation, contraction's cheaper read, recomputation where producer/consumer multiplicity sets its cost; a choice changing an assumed kind or structure takes the guard/miss path (part 10). A read walks `Live::of` (part 3) and runs its graph's plan from the 1 cache (part 10); only a miss schedules, so a graph seen before costs a key, a probe and the run (cost model); a hit at unchanged lengths copies no description or map. A lone node is a graph of 1 (JAX's 1 compiled program per primitive and signature; tinygrad's kernels cached by structure). Stage ends (tinygrad): a node whose length the data decides ends its stage, its count read before later steps are laid out; a value fuses with its 1 consumer, or with consumers indexing it alike; consumers indexing it differently store it unless it's recomputed. Recomputable in each consumer's stage: an elementwise chain over inputs and recomputed values, each node at most 1 copy in its row's `cost` (XLA's producer fusion, AOTAutograd's recomputation); structural where item counts cancel, else run preparation compares actual producer/consumer multiplicity; coefficients retained by unrolled reverse AD follow the same rule. Past the graph, reference counts release each name and temporary at its last use. Select normalization supplies ordinary masked or compacted operations (part 14). Error recording and checkpoints: Measures; data errors never restart a read; descriptions are immutable. An uncompiled stage stores outputs in its own pass. A fold whose rows hold more than 1 item adds each row into column totals (NumPy `sum(axis=0)`), a compiled kernel into its piece's totals; `inner == 1` keeps totals in registers. A fold whose totals feed later steps runs with them cell by cell where a cell fits the stage buffer (`fusion::BUFFERED`), the later steps reading its totals and items from cache; the plan chooses this schedule, never the compile decision. Run preparation uses compiled code when available, else compiles the selected recipe once actual items × passes saved × saving per item × expected runs (loop counts, a recording's reuse) exceed the compile cost; meanwhile the same stage executor runs its blocks uncompiled. Forcing reads, the 1 list: display; conversion to Python or JavaScript; writers (`•json⁻`); an observing read (a guard's predicate, a count or length, an item read one at a time, `⍎`'s text); a call's read of its own operands (part 5's roles: a parameter, values deciding a length or a value-dependent kind; selective assignment's positions, part 16; a call on host values, part 5); an effect, run at its call outside a recording (what it reads, after the effects before it); the memory limit, in bytes: a pending graph whose kept-alive leaves (buffers only it still references) pass `MAX_KEPT` (8) times its largest array, or whose own nodes pass 1 MiB (about 7,000 nodes; one core's L2), never a count of calls. A top-level statement's end and destructuring force nothing. Running a graph and reading its result are separate steps (tinygrad's `realize()`, `numpy()`): on a GPU a run submits work and a read waits for readback. Not: a 2nd scheduler or general multiversioning framework; eager executors beside it; a step routed around it to its kernel by kind; lone-call paths beside the pipeline; a plan cache beside the 1 cache; a session registry forcing pending work at effects; executor arms calling own code; masked-error retry state, revised-origin decisions, forwarding, protected retry roots or carries; replay of completed effects or random counters. 9. Block stages, JIT (`program/blocks.rs`, `program/blocks/`, `jit.rs`, rustymath `gather.rs`, `agreement.rs`, `reduce.rs`). A stage runs its steps a block of items at a time and compiles by part 8's formula. Rustymath's 1 numbered-source `ReadPlan` serves kernels, folds and block stages alike (periods, tiling, runs): it routes `(source, position)` through positional mappings and binds run-time source views once, scalar fallbacks included: a fallback reads another scalar source; mappings hold no BPL `Value`; binding never mutates a cached map; the consumer supplies its run length. Lowering reads the object's common formula and repair predicate (part 6): unless ranges proved the predicate false, the JIT branches only when a vector has flagged lanes, calls the object's scalar repair for those lanes alone, and makes the corrected result available before downstream fused operations; there is no JIT-specific repair threshold or mathematical implementation. `jit::KERNELS` owns compiled code, keyed by emitted structure, types and modes, never by counts, which are run-time arguments. Run preparation compiles on demand only the useful recipe the actual counts select (part 8's formula), never every possible version; different counts share code unless they select different emitted code; a selected version keeps its shared handle (`Arc`) after the first lookup, and plans hold references to that code, never another code cache or copy. Block stages read Booleans, fixed integers, ordinary machine integers, f32, f64, and characters as `i32` code points (`characters::as_codes`, no copy); a step giving characters, f16 steps and complex steps run on rustymath's whole-array kernels outside block stages, and a chain doesn't fuse across them. Kernels preserve kinds, explicit conversions, exact results and the documented accuracy bounds; they may reassociate fold or product additions and use FMA for `a×b+c`, overflowing differently near the range limits; last bits, infinities and NaNs, and zero's sign need not agree across routes; a dfn fold keeps its specified order. The JIT generates code with Cranelift, natively only (the browser runs block stages uncompiled until the GPU stage); LLVM is never a target; the built-in JIT works without an external backend (part 23). Not: a stage's own cache of compiled code; kernel sightings as a compile rule; basedpl copies of rustymath's read planning; thresholds per consumer; a primitive → machine-op table; fold admission re-derived beside row facts. 10. Traces, recordings and cache (`program/trace.rs`, `program.rs`, `program/run.rs`). A trace records the graph that actual calls build; it is no dispatch mode or numerical evaluator. Inputs enter opaque and unforced. Captured free data, operator data and records are snapshotted once at the call and traversed with Trees; replay and deferred execution bind these current inputs, never rereading names or comparing separately kept free values. Callable templates: part 19. A recording holds the ordinary Program as a data-free graph template, with its key and legality guards. Binding a cached call instantiates that template with current data through shared node construction, without loaning or refilling a native execution Plan; only a read schedules and executes the graph; case admission inspects that same instantiated graph, not another native plan. 1 Programs store owns function recordings, stops, Select plans and forced-graph plans: dfns key by their shared Definition, weakly owned, never by closure identity; forced-graph entries without a function owner are evicted least recently used past 256; the JIT code table separately owns compiled code, not plans. Description-backed planning for Select and typed-body admission uses the same ordinary call boundary on unbound native inputs with real descriptions; reading their items ends planning. Typed-body admission reuses the same key, capture snapshot and guard checks, not a private recording lookup or capability store. Unknown host structure ends recording before the normal host call runs once, never creating a fake scalar/empty-layout metadata Value. The read chokepoint distinguishes Count, Structure and Value reads and their input dependencies; unbound linear reads stay forbidden; description-only references neither execute nor capture callable contents. Recording stops, by the ordinary reason, at argument-dependent structure or control, host-kind data, guards, recursion, execute, error guards, outer writes and binding/settings effects; a body whose recording can't represent or serve it still builds the same graph through the same operation entry. Broader cache admission, under arbitrary older levels or for every host/function-valued result, is a scope question: it needs a demonstrated and discussed common-use cost or behaviour, never extra protocols. Keys hold structural origins, literal values, function structure, numeric input kinds/layout facts (opaque inputs by kind bounds: part 2), axis equalities and parameter ReadKind. Data-input axis lengths key by 0/1/more class, with no buckets; array counts (take/reshape/window sizes) refill at concrete lengths through the row factory; rank, axis, permutation and other structural parameters key by value, as does a count deciding how many calls unroll, never by length class; data-dependent control that can't be keyed safely stops recording. A hit binds current inputs, checks key and legality and refills changed descriptions and maps, never gathering, rewriting, ordering or placing stages; invalidated kinds, widths, layouts, structural parameters, equalities or proofs take the normal miss path, never speculative contracts. Compiled code stays count-parametric. A retrace limit of eight counts genuine signature changes. Data-dependent array lengths: visit Count roots in chronological Parameter order, schedule only through the next unobserved count, extend the same cache key and return `Ran::Rest`; the same Running prefix keeps completed effects and draws; a hit refills the suffix, a miss schedules it. Drawn axis names are numbered by first appearance and rebound to current names on replay. Output effects and draws with described results record normally. Not: a 2nd recording builder, store or cache; live closure data in cached programs; late free-name reads; numeric input range contracts; synthetic metadata Values for output reconstruction; generic residual count ports, an output count-index field or Counted multi-output metadata for loop residuals; retry or replay of completed effects; speculative kinds; another length type or bounded-shape framework. @@ -113,12 +113,12 @@ Each: job; callers; not (what must not exist). `meta/foundation-reset.md`'s "Del 13. Host iteration (`eval/operators/power.rs`, `eval/operators/folds.rs`; released Power and dfn fold/scan algorithms). Power, dfn fold and dfn scan, resolved by `OperatorInfo`, apply their actual function values in 1 language-level iteration home, reusing the released count, count-array, negative-count, predicate, convergence and history algorithms. Each body call takes the ordinary bound function entry, keeping current origin, captures, mode and settings, and can reuse its recording from the 1 cache. Under AD the actual body calls build ordinary primal and linear graph steps as execution proceeds, as the released unrolled AD did. A count array keeps the initial state at 0 and the requested states in their original order; inverse counts and negative infinity apply the inverse body. Infinite Power uses `Numeric::convergence()` and compares a whole logical carry, not independently frozen numeric items. Arbitrary dfn folds keep right-to-left calls; arbitrary dfn scans keep left-to-right calls, their seeds, identities, empty cases and output structure. Independent lanes batch through the ordinary map entry. A body admitted by part 19's shared typed-body query uses the same primitive facts, identities, optimized Fold steps and algebraic floating-point freedoms regardless of its spelling. Where the primitive permits reassociation, as `+/` does, cancellation can change more than the last bits; substitution adds no freedom to reorder non-associative operations or effects. Arbitrary bodies retain ordered calls. Empty-fold admission derives scalar argument descriptions from the existing kind, never a sampled cell or allocated dummy array. A count deciding how many body calls unroll is structural (part 10): array-length Count refill never lets an unrolled three-call graph serve four calls. A data-dependent predicate reads primals and follows the ordinary recording-stop rules, still computing the right derivative of the executed path. Histories use ordinary compact/mixed assembly; their language behaviour isn't deferred. Effects occur on every actual body invocation. Not: `Step::Loop`, count-1 wrappers or loop IR; loop-specific JVP/transpose; compact-loop AD, primal/residual/linear body splits, carry-and-residual-count packets, history-axis metadata protocols, checkpointing; a native iteration compiler or repeat kernel lowering; a 2nd iteration runner; restoring the old Linear evaluator. 14. Selects (`program/control.rs`). `⍚`, `f@[p]` and guards on framed values normalize at the semantic-form boundary, before levels, into ordinary graph operations. Case admission takes the single cached planning route from descriptions (part 10), planning cases on whole arguments. Masked execution needs item-local dependencies, no effects and 1 common output layout and kind; under dynamic kinds, no value-dependent result kind; under checked dynamic execution, no case able to record a data error (part 6). Otherwise compact from the start. Static graph-numeric cases need no data-error admission check. Ordinary Choice/Picks assemble masked outputs; the selector is run-time data, not differentiated; Choice is jointly linear in its choice values. Compaction gathers each case's selected cells, calls the original bound function once on that whole group and scatters its results; a case may reduce its group and fill the selected positions, with no implicit per-cell map; a mask with fewer axes selects whole cells. Under `∂`, gate each active argument, capture and tangent direction before calling the original bound function through the normal call entry: `select(mask, x, stop_gradient(x))` for primal inputs, Choice against zero for directions, derivative-marked Pending directions included (JAX's double-`where` protection against an inactive infinite slope). Planned primal case bodies establish admission but never replace actual calls under AD, which must honour attached derivative rules. Both forms use ordinary graph AD and `linear::read_back`, accumulating at repeated positions; reverse uses the actual forward's coefficients, never replaying a case to rediscover its branch. A selector read deciding structure follows the normal recording boundary, with no exemption from acceptance or cost requirements. GPU lowering can later use a per-lane branch. Not: an opaque Select Step or executor; Select-specific JVP/VJP wrappers or capture-gradient projections; a 2nd case planner; admission by spelling or narrowed ranges; masked-error retry; cross-Program case splicing beside normal function invocation. 15. `∂` (`derivative.rs`, `linear.rs`; JAX `jvp`, `linearize`, `transpose`). Derivative levels and tags give forward, reverse and higher-order differentiation; tags, escaping-value stripping, custom JVP rules (`f∂←g`), `•detach`, nested AD and batching stay supported. Differentiated numeric leaves promote to real-floating kinds by the normal float-width rules, and seeds take the promoted input kind; parameters and nonnumeric structure stay unchanged; complex AD stays unsupported until its convention is agreed. Primitive JVPs come from rustymath partials through rows; zero-tangent operations run on primals in 1 dispatch rule. Linear nodes are ordinary graph steps over unbound seeds, their derivative level on the node, not its description or cache key. Forward mode binds a seed; reverse mode transposes the ordinary linear steps in 1 pass using row linearity facts. Reuse the released argument/parts traversal and Jacobian reconstruction, not its separate Linear/Op evaluator. A Jacobian uses the normal map entry over unit seeds, choosing rows or columns from actual dimensions. Ordinary graph coefficients keep the actual intermediate values the derivative needs. A recorded linear function walks the ordinary graph bounded by its derivative level: nodes outside that level become inputs holding the actual current coefficient Values, their pending graphs and older levels included; current seeds are argument inputs. Body calls made by Power and dfn folds/scans unroll into these same steps (part 13). A shared frame input uses normal spreading, whose transpose sums across the frame. 1 `linear::read_back` transposes numbered-source read plans, reading where positions are unique and accumulating where they repeat; copies, joins, selections and fills share it. Select input gating follows part 14. Unbound linear reads are refused at the read chokepoint. Native floating replacement and additive writes differentiate through the same At and read transpose rules: positions are nondifferentiated primal choices; replacement requires distinct destinations; addition accumulates every repeat; broadcast cotangents reduce to the original RHS shape. Structural, shape-changing and host-kind writes have no derivative and raise Unsupported when differentiated; unrelated nondifferentiated writes stay ordinary work. Not: a separate AD evaluator; a separate primal/linear Program partition, coefficient reconstruction, tape, registry or packet codec; residual-history outputs, reverse-loop programs or checkpoint passes; loop-specific derivative body wrappers or packing; linearization by replaying the user's function to rediscover its executed branch; entry-by-entry Jacobians; Rust closures standing in for BPL functions. -16. Writes (`selection.rs`, `eval/assign.rs`, compiled targets in `eval/binder.rs`, `eval/tree.rs`). Supported element writes, whole-item replacement and nested selection composition compile through 1 mechanism; adding identity, enclosure or another supported lhs composition never stops recording or reruns the selector in the interpreter on replay; any speed exception needs Jeremy's agreement on the concrete feature boundary and an efficient alternative. 1 selection owner keeps `g`'s read recipes through the pending update and composes them back to the root in the same Program; ordinary reads use computed-value caches, address demand the retained recipes. Each affected numeric leaf uses `At`, inside and outside traces; Mixed containers and output Trees rebuild the outer structure from the updated children; whole-item replacement substitutes a child value without a numerical write; the scheduler chooses in-place execution. Direct paths (`⌷ .[] ⊃` chains) are compositions inside the walk. Key extension (`m.k←v`, a missing key) rebuilds the containing structure with the new item. Positions (`f@[p]`, a mask) are a selection by `p`, composed into the walk. A `g` that computes rather than selects writes through `g⁻`; a `g` that adds items (`{⍵,0}`) raises "cannot assign to an item that isn't from the original", while a catenation of selections works (`10@{(1↑⍵),¯1↑⍵}`, `((1↑a),¯1↑a)←8 9`). Nested selections use logical item paths and numeric-leaf reads; logical source paths identify whole native children; native read views stay element selections even over a whole leaf; general nested-to-flat selector construction uses the shared structural-read boundary. At a repeated position, which value a replacing write keeps isn't promised, keyed or not (decisions 6.2, 15); an accumulating write (`y.[dst]+←v`) adds every repeat. Checked selective assignment validates every position before changing anything, so an error leaves the name unchanged; static graph-numeric writes skip invalid numeric positions instead; structural and host-kind errors stay checked (Measures). Positions force; values may stay pending; a trace checks positions when its `At` runs. Writes into exact-rational or function leaves are explicitly unsupported in this release; native-to-boxed replacement works through the host-kind recording stop. Computed whole-Mixed-root writes can't change the outer shape or keys (use ordinary root assignment); child replacement may change internal shape or keys; selected outer positions don't fix their children's internal shapes. Not: a direct route beside the walk; labels; numerical write loops in structural code; write executors or numerical host writers beside `At`'s; appends chosen by spelling; a segmented entry/leaf address protocol; a 2nd selector evaluator or interpreter fallback. +16. Writes (`selection.rs`, `eval/assign.rs`, compiled targets in `eval/binder.rs`, `eval/tree.rs`). Supported element writes, whole-item replacement and nested selection composition compile through 1 mechanism; adding identity, enclosure or another supported lhs composition never stops recording or reruns the selector in the interpreter on replay; any speed exception needs Jeremy's agreement on the concrete feature boundary and an efficient alternative. 1 selection owner keeps `g`'s read recipes through the pending update and composes them back to the root in the same Program; ordinary reads use computed-value caches, address demand the retained recipes. Each affected numeric leaf uses `At`, inside and outside traces; Mixed containers and output Trees rebuild the outer structure from the updated children; whole-item replacement substitutes a child value without a numerical write; the scheduler chooses in-place execution. Direct paths (`⌷ .[] ⊃` chains) are compositions inside the walk. Key extension (`m.k←v`, a missing key) rebuilds the containing structure with the new item. Positions (`f@[p]`, a mask) are a selection by `p`, composed into the walk. A `g` that computes rather than selects writes through `g⁻`; a `g` that adds items (`{ω,0}`) raises "cannot assign to an item that isn't from the original", while a catenation of selections works (`10@{(1↑ω),¯1↑ω}`, `((1↑a),¯1↑a)←8 9`). Nested selections use logical item paths and numeric-leaf reads; logical source paths identify whole native children; native read views stay element selections even over a whole leaf; general nested-to-flat selector construction uses the shared structural-read boundary. At a repeated position, which value a replacing write keeps isn't promised, keyed or not (decisions 6.2, 15); an accumulating write (`y.[dst]+←v`) adds every repeat. Checked selective assignment validates every position before changing anything, so an error leaves the name unchanged; static graph-numeric writes skip invalid numeric positions instead; structural and host-kind errors stay checked (Measures). Positions force; values may stay pending; a trace checks positions when its `At` runs. Writes into exact-rational or function leaves are explicitly unsupported in this release; native-to-boxed replacement works through the host-kind recording stop. Computed whole-Mixed-root writes can't change the outer shape or keys (use ordinary root assignment); child replacement may change internal shape or keys; selected outer positions don't fix their children's internal shapes. Not: a direct route beside the walk; labels; numerical write loops in structural code; write executors or numerical host writers beside `At`'s; appends chosen by spelling; a segmented entry/leaf address protocol; a 2nd selector evaluator or interpreter fallback. 17. Effects, draws. Effects and draws are always nodes, inside and outside traces. Every effect (output, system effects, time, input `⎕`) is an ordered node: effects run in order among themselves, each taking the token of the effect before it, and an effect forces only what it reads (JAX's ordered effects; when other pending work runs isn't promised); a stage ends at each effect node. Outside a recording every effect runs at its call, after the effects before it; a call whose body holds an effect is itself an ordered effect; host iteration invokes effects on every executed body call, in order, with no cached loop node. An effect that changes bindings or settings, or gives a value read as structure (input `⎕`, `•load`, `⍎`, settings changes), ends a trace's recording; pending nodes keep the settings they were built with (part 4). Effect kinds: `Acts` (output, input `⎕`, system functions that read or write files, the network, the clock or the terminal, or make something new on each call: `•delay •nput •fetch •nget •time •load •js •uuid`); `Catches` (an error guard, which forces the work built inside it at its exit; a call on prototypes); `Writes` (a write to a binding outside a level's frames). `⍕` is pure: a carried argument goes to its level before a step; `∂` formats primals (a zero-tangent call); batching stops and formats cell by cell. A draw is a `Draw` node, computed when a read reaches it; map nesting comes from the levels. The origin captures 1 opaque generator handle as a data input, its state owned by rustymath's `random.rs`; its key and counter are run-time data, never structural parameters; capturing or binding a generator never advances its counter; `•prefs` with a seed changes the generator key. Each whole `Draw` reserves 1 stream from its captured generator when it executes; its first read computes the whole draw, which later whole or partial reads reuse; a replay executes a new draw and reserves a fresh stream; deferred read order can decide which draw receives which stream. A batched draw enumerates the whole result in canonical flat item order through rustymath's `Items::new` stream, each item with disjoint word positions and rejection space; frame geometry determines result placement. Equal seeds don't promise equality between different batched/unbatched spellings. Not: host draws or effects beside their nodes; a session registry forcing every pending value at an effect; draws stopping traces; replays repeating draws; a 2nd home for a generator counter; a draw context beside the levels; a 2nd seeded generator; another RNG hierarchy (a special first-cell stream, cell-stream fold or separate cell rejection mapping); a body-upfront draw count, c+k place or replay counter reservation. -18. Binder, executor (`eval/binder.rs`, `eval/tree.rs`). The binder compiles each statement before it runs, emitting instructions only; `tree.rs` is the 1 statement evaluator and holds the value constructors, through which the compiler may build values from constants (constant folding: `⍤1` derived once): construction, not evaluation. A top-level statement compiles, then runs once. Numeric tokens are interpreted under the active Numeric when their containing statement runs; a bound value can therefore be constructed before a dfn's body literal. Keep that single timing rule rather than adding literal environments or rewriting shared Definitions when functions are constructed. Binding rules: (1) a call's result is a value to the binder, as is a field read from it; a subscript's selection counts as a call: `+/÷(f 0)` is an expression; `g←f 0 ⋄ +/÷g` names a returned function; `1⍚fs x` calls a selected one. (2) A role change the compiler can't see is an error (decision 10), never a restart: a write that changes the role of a name outside the writing function's frame (raised at the write), and a read, later in a statement, of a name whose role that statement's write changed (raised at the read); an array binds as a function when any item is a function; `Session::assign`, the 1 home of assignment, compares old and new categories. (3) `⍎` changing a name's role while a statement that reads it runs is an error (decision 9); an unbound name binds as an array (`x+⍎"x←3"` works). An earlier assignment in the statement (`x+(x←3)`) gives the name the assigned expression's role for the rest of it. Roles are read again at each statement's start; a recording checks the roles of the names it reads at the start of the statement and of each group or run. The compiler binds ahead: dot access on a name (the field's role from `T`'s value, a recorded fact); a scope `T.(expr)` (from `T`'s keys and field roles; a computed `T` compiles its body on entry, as `⍎`'s text does); superscripts; numeric subscripts by their operand's role (`(⊃fs)₁` selects); an absent `⍺` (compiled per valence, or checked at the statement's start). Using a call with no result as a value fails when it runs. Not: evaluation inside the binder; resume machinery; a 2nd node evaluator; a 2nd reader of the grammar. +18. Binder, executor (`eval/binder.rs`, `eval/tree.rs`). The binder compiles each statement before it runs, emitting instructions only; `tree.rs` is the 1 statement evaluator and holds the value constructors, through which the compiler may build values from constants (constant folding: `⍤1` derived once): construction, not evaluation. A top-level statement compiles, then runs once. Numeric tokens are interpreted under the active Numeric when their containing statement runs; a bound value can therefore be constructed before a dfn's body literal. Keep that single timing rule rather than adding literal environments or rewriting shared Definitions when functions are constructed. Binding rules: (1) a call's result is a value to the binder, as is a field read from it; a subscript's selection counts as a call: `+/÷(f 0)` is an expression; `g←f 0 ◇ +/÷g` names a returned function; `1⍚fs x` calls a selected one. (2) A role change the compiler can't see is an error (decision 10), never a restart: a write that changes the role of a name outside the writing function's frame (raised at the write), and a read, later in a statement, of a name whose role that statement's write changed (raised at the read); an array binds as a function when any item is a function; `Session::assign`, the 1 home of assignment, compares old and new categories. (3) `⍎` changing a name's role while a statement that reads it runs is an error (decision 9); an unbound name binds as an array (`x+⍎"x←3"` works). An earlier assignment in the statement (`x+(x←3)`) gives the name the assigned expression's role for the rest of it. Roles are read again at each statement's start; a recording checks the roles of the names it reads at the start of the statement and of each group or run. The compiler binds ahead: dot access on a name (the field's role from `T`'s value, a recorded fact); a scope `T.(expr)` (from `T`'s keys and field roles; a computed `T` compiles its body on entry, as `⍎`'s text does); superscripts; numeric subscripts by their operand's role (`(⊃fs)₁` selects); an absent `α` (compiled per valence, or checked at the statement's start). Using a call with no result as a value fails when it runs. Not: evaluation inside the binder; resume machinery; a 2nd node evaluator; a 2nd reader of the grammar. 19. Function values (`eval.rs`, `eval/operators.rs`). Leaves: primitive, system, dfn, late-bound name and function array. An operator application, primitive or dfn operator alike, is 1 Applied variant holding its operator and operands; trains are 1 variant holding their tines, and a train's array tine stays ordinary data. Attached inverse/JVP rules belong to FunctionData beside its environment. OperatorInfo owns call, semantic-form resolution, operand classification, spelling, inverse, discards and Python name; declared fold/product operations and bitwise resolution use this table, not parallel switches in Function. Function dot/field/index reads are operator kinds with their selector parameter. Framed is an internal function kind for ordinary batching. A Recorded function references an ordinary Program and its current input bindings, never a compact-loop body or residual packet. 1 traversal, the released shared Rebuild/visit_operands, serves capture, template, rebuild and attached rules through its actions; 1 OperandKind rule tells structural operands from data, ranks included; callable/data/metadata roles are classified before capture; description-only references keep their metadata without traversing functions they happen to contain. All executable free data are call-time snapshots in 1 capture order; FunctionOperand distinguishes actual values from positional inputs; a literal operand's value is its literal node; a recorded body takes its captures and arguments as data (`Value::data`), and its key holds by value any literal node that still reaches an input. Cached templates strip run-time environment and context and hold no current producer or captured data; current bindings supply the data when called; actual function results rebuild once at the function boundary. Host results this representation can't hold take the normal host boundary, never universal closure conversion. Nested AD and custom rules stay correct. Higher-order consumers obtain known primitive facts through 1 typed-body query at the requested arity and cell descriptions. Declared primitive and bitwise constructors use the constant-time base case. Other bodies reuse the existing isolated description-backed planning run and guarded Program cache. An admitted body has 1 plain native result from the exact original primitive call, direct ordered formal references with identity read maps, and matching numeric mode and declared settings. A conversion, wrapper, observed argument/control dependency, effect or custom derivative prevents substitution; consume the capture walk's recursive custom-derivative fact rather than adding a traversal. All nonliteral native captures become the existing description inputs before planning; nonliteral host data or structural captures conservatively decline before the body runs. Literals, callables and settings retain their established provenance. A declined query uses the ordinary function and iteration process. No new IR, capability cache, guessed identity or per-consumer dfn recognizer is permitted. Not: per-wrapper FunctionNode variants; attached-rule wrapper nodes; duplicate child/name/rule traversal; live-name replay reads; a 2nd template IR or registry; loop-specific Recorded input/output projections; Rust closures as BPL bodies; another call route for trains. -20. Index notation and positions (`⍳`, `•index`, rustymath selection planning). Level 2: regular selectors as strides, in rustymath's selection planning; level 3: contractions as 1 rule (part 7). Positions count from 0, negative ones from the end; a numeric position outside its axis raises INDEX ERROR under checked dynamic execution and reads the fill under static graph-numeric execution, an empty axis included (Measures); a NaN position reads the fill in either mode. Missing keys and other structural errors resolved at the boundary stay checked (keyed agreement's fill: part 2). `.[]` takes no `⍠`; a fill comes through `⌷⍠["fill":v]`. Drawn names: part 10's key; a map whose body returns one: part 12. Axis order of `g×z`: the right's order where every axis of the left operand pairs and the right has more axes; otherwise the left's axes first. Not: range inference (`•index ∞`, decision 11); strides decided from 1 run's index values inside a recording. -21. Scripts and marks (`number.rs` `MARKS`, `syntax.rs`). Marks, 1 table (`MARKS`): `ₓ` ordinary machine integer, `ᵣ` unbounded integer/rational, `ₓ₁ ₓ₂ ₓ₄ ₓ₈` fixed widths, `ₕ ₛ ₚ` floats of 16, 32 and 64 bits, `ₐ` real; after a function each is an operator kind (part 19), after a value a conversion node (`Mark::conversion`). A function mark normally converts each result as its producer's stage stores it: `⌊ₓ₁`, `-ₓ₁`, `÷ₛ` and `⍳ₓ₁` take no 2nd pass; other marked functions convert their result. A marked literal group, or a superscript on a literal number (`3²`), folds through the same conversion or call (part 18), never in the lexer or parser. `ᵣ`: `5ᵣ` is `5ᵣ1`; a trailing `ᵣ` converts a value into the unbounded domain, a finite real float exactly from its stored value, without recovering decimal intent; denominator 1 stays in that domain and displays as `3ᵣ`, never `3ₓ`; `xᵣy` floors its components without machine narrowing and uses the strict rational-construction division; ordinary `÷ᵣ` follows the call's Mathish policy; Python/JSON conversion rules stay: already-unbounded external values enter this domain without a BPL annotation. `ₐ` gives real floats at the arguments' float width, never a width chosen from values: `ₓ₁ ₓ₂` promote to float32, `ₓ₄ ₓ₈` to float64; a weak literal takes its partner's width; no compact join takes the default width. Outside the real domain it gives the principal value's real part (`√ₐ¯4` → `0`, `⍟ₐ¯1` → `0`, `¯8*ₐ÷3` → `1`). Result semantics other than division: `fₐ` ≡ `(f args)ₐ`; division selects float arithmetic before computing. The marked-form kind entry resolves early, before the unmarked producer's kind rule, operand reads or level handling (part 5): the primitive forms `√ₐ *ₐ ⍟ₐ ○ₐ` and inverse-circle real forms to rustymath's real operations (`RealSqrt`, `RealLn`, `RealPow` and `RealCircle`'s code table); machine-integer `⌊ₓ ⌈ₓ ÷ₓ`; float division `÷ₐ`; rational division `÷ᵣ`, which selects the exact domain before the division, never converting its floored result (integer `÷` already floors). Each real object holds its real form, the real part of its complex form, formula (`Facts::formula`), range rule and real-part slopes: `RealLn` has 1/x; `RealPow` below 0 differentiates |x|^y cos πy; `RealSqrt` and circle codes whose real part is constant outside the real domain have slope 0 there; infinite slopes use JAX's double `where`. Resolved operations use normal dispatch and the objects' derivative facts; their origins keep the marked call. These mandatory semantics are not late speed rewrites. No kind is speculated (part 2); the listed primitive real forms, ordinary integer `÷` and `÷ₓ` are guaranteed fast. Numeric superscripts on functions mean Rank: `f¹` ≡ `f⍤1`, `f⁰ ¹` ≡ `f⍤[0 1]`, `f⁻¹` ≡ `f⍤¯1`; one, two or three signed entries use Rank’s normal rules. Bare `f⁻` is inverse; `f⁻ ¹` ranks the inverse. Numeric-array superscripts remain powers, including `x⁻¹`. Iteration uses `⍣`. Letter superscripts: `ᵀ` transposes an array; `ᵘ` makes a scalar holding a function; either on the other role is an error. +20. Index notation and positions (`ι`, `•index`, rustymath selection planning). Level 2: regular selectors as strides, in rustymath's selection planning; level 3: contractions as 1 rule (part 7). Positions count from 0, negative ones from the end; a numeric position outside its axis raises INDEX ERROR under checked dynamic execution and reads the fill under static graph-numeric execution, an empty axis included (Measures); a NaN position reads the fill in either mode. Missing keys and other structural errors resolved at the boundary stay checked (keyed agreement's fill: part 2). `.[]` takes no `⍠`; a fill comes through `⌷⍠["fill":v]`. Drawn names: part 10's key; a map whose body returns one: part 12. Axis order of `g×z`: the right's order where every axis of the left operand pairs and the right has more axes; otherwise the left's axes first. Not: range inference (`•index ∞`, decision 11); strides decided from 1 run's index values inside a recording. +21. Scripts and marks (`number.rs` `MARKS`, `syntax.rs`). Marks, 1 table (`MARKS`): `ₓ` ordinary machine integer, `ᵣ` unbounded integer/rational, `ₓ₁ ₓ₂ ₓ₄ ₓ₈` fixed widths, `ₕ ₛ ₚ` floats of 16, 32 and 64 bits, `ₐ` real; after a function each is an operator kind (part 19), after a value a conversion node (`Mark::conversion`). A function mark normally converts each result as its producer's stage stores it: `⌊ₓ₁`, `-ₓ₁`, `÷ₛ` and `ιₓ₁` take no 2nd pass; other marked functions convert their result. A marked literal group, or a superscript on a literal number (`3²`), folds through the same conversion or call (part 18), never in the lexer or parser. `ᵣ`: `5ᵣ` is `5ᵣ1`; a trailing `ᵣ` converts a value into the unbounded domain, a finite real float exactly from its stored value, without recovering decimal intent; denominator 1 stays in that domain and displays as `3ᵣ`, never `3ₓ`; `xᵣy` floors its components without machine narrowing and uses the strict rational-construction division; ordinary `÷ᵣ` follows the call's Mathish policy; Python/JSON conversion rules stay: already-unbounded external values enter this domain without a BPL annotation. `ₐ` gives real floats at the arguments' float width, never a width chosen from values: `ₓ₁ ₓ₂` promote to float32, `ₓ₄ ₓ₈` to float64; a weak literal takes its partner's width; no compact join takes the default width. Outside the real domain it gives the principal value's real part (`√ₐ¯4` → `0`, `⊛ₐ¯1` → `0`, `¯8*ₐ÷3` → `1`). Result semantics other than division: `fₐ` ≡ `(f args)ₐ`; division selects float arithmetic before computing. The marked-form kind entry resolves early, before the unmarked producer's kind rule, operand reads or level handling (part 5): the primitive forms `√ₐ *ₐ ⊛ₐ ○ₐ` and inverse-circle real forms to rustymath's real operations (`RealSqrt`, `RealLn`, `RealPow` and `RealCircle`'s code table); machine-integer `⌊ₓ ⌈ₓ ÷ₓ`; float division `÷ₐ`; rational division `÷ᵣ`, which selects the exact domain before the division, never converting its floored result (integer `÷` already floors). Each real object holds its real form, the real part of its complex form, formula (`Facts::formula`), range rule and real-part slopes: `RealLn` has 1/x; `RealPow` below 0 differentiates |x|^y cos πy; `RealSqrt` and circle codes whose real part is constant outside the real domain have slope 0 there; infinite slopes use JAX's double `where`. Resolved operations use normal dispatch and the objects' derivative facts; their origins keep the marked call. These mandatory semantics are not late speed rewrites. No kind is speculated (part 2); the listed primitive real forms, ordinary integer `÷` and `÷ₓ` are guaranteed fast. Numeric superscripts on functions mean Rank: `f¹` ≡ `f⍤1`, `f⁰ ¹` ≡ `f⍤[0 1]`, `f⁻¹` ≡ `f⍤¯1`; one, two or three signed entries use Rank’s normal rules. Bare `f⁻` is inverse; `f⁻ ¹` ranks the inverse. Numeric-array superscripts remain powers, including `x⁻¹`. Iteration uses `⍣`. Letter superscripts: `ᵀ` transposes an array; `ᵘ` makes a scalar holding a function; either on the other role is an error. 22. Reports (`explain.rs`, rustymath `events`). `•explain` reports every graph, with no size filter; users filter by `items`. Records: `fused` (stages run); `stored` (every value a stage end stores partway, with a reason); `forced` (pending work that a read forces partway, with a reason: an observing read, a host kind, an effect's operand, a selective assignment's checks); `programs` (every recording: ordinary iteration bodies, maps' operands, `∂`'s operands; events include "retraced" and "rerun", as JAX's cache-miss explanations and Dynamo's recompile logs report); `stopped` (a row per reason with calls, items and `site`, as Dynamo's `break_reasons`); `fast` (each rule applied); `fallbacks` (every run per item, cell or iteration on the host, with reasons); `declined`, `accumulations`, `mixed`, `compiles`, `routes`. `Facts::reads`, 1 home, classifies a reader that takes its argument whole (sorts, grades, searches, `⌹`, products). Routes are rustymath's `events`, their 1 home, which basedpl's `•explain` and rustymath's Python `rm.explain()` both capture; the route list holds only routes the target keeps. Route checks and the coverage matrix keep 3 requirements: a mapping stage compiles (`compiled`), a fold reads its argument inside its stage (`fused fold`), a gradient replays (`trace`); `⍝⍝⍝ route` names only these 3, and every other route is checked by the absence of slow events (`fallbacks`, `stored`, `forced`, `stopped`), which fail unless allowed. Reports and diagnostic tools change without agreement. 23. Later stages' requirements on the core (not built in this release). GPU: running a graph separate from reading its result; nodes as plain data; transposes that read rather than scatter where they can; readbacks for results whose shape depends on values; bf16 as a kind (spelling open); WGSL has `bool i32 u32 f32` and `f16`, no f64, i64, complex or bf16 (those stay on the CPU or are emulated: the GPU stage's choice); checked data errors gathered into 1 flag read once at the end (the session slot's device form; XLA's error bits); buffer capacity independent of plan keys, with captured graphs reused while their buffer addresses stay valid and recaptured when a capacity change requires it; user bounds for lengths the data decides (JAX's `size=`); kernels that read counts from device memory. Array files: leaves backed by buffers the allocator doesn't own: rustymath `Buffer`'s owner `Owned(Vec)` or `External(Arc, offset, len)`, read through `&[T]`, copied on its 1st write; a region says whether the host can read it; built with array files. External backend (mo): the graph as plain data that a backend writes out. diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 847d3483..74fc6514 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -2,7 +2,7 @@ use basedpl::{EvalOptions, Evaluation, Input, InterruptHandle, Output, ParseStat use rustyline::error::ReadlineError; use std::{io::{self, IsTerminal, Read, Write}, sync::{Arc, Mutex}}; -const USAGE: &str = "Usage: bpl [-e EXPR [ARG...] | FILE [ARG...] | - [ARG...] | --worker | --costs PATH | --kernel -f CONNECTION_FILE]\n\nNo arguments: persistent BPL REPL (Ctrl-D to exit, Ctrl-C to cancel input or interrupt an evaluation).\nType `name then Tab or a non-letter to enter a symbol, e.g. `iota5 becomes ⍳5.\nUse - to execute all of stdin as one source.\n`•host \"args\"` gives the ARGs.\nUse --worker for JSON-lines requests with deadlines and interruption.\nUse --costs PATH to time a BPL speed file or directory, printing each cost and this machine's budget.\nUse --kernel -f CONNECTION_FILE to run a Jupyter kernel.\n"; +const USAGE: &str = "Usage: bpl [-e EXPR [ARG...] | FILE [ARG...] | - [ARG...] | --worker | --costs PATH | --kernel -f CONNECTION_FILE]\n\nNo arguments: persistent BPL REPL (Ctrl-D to exit, Ctrl-C to cancel input or interrupt an evaluation).\nType `name then Tab or a non-letter to enter a symbol, e.g. `iota5 becomes ι5.\nUse - to execute all of stdin as one source.\n`•host \"args\"` gives the ARGs.\nUse --worker for JSON-lines requests with deadlines and interruption.\nUse --costs PATH to time a BPL speed file or directory, printing each cost and this machine's budget.\nUse --kernel -f CONNECTION_FILE to run a Jupyter kernel.\n"; /// Writes each output to stdout as the evaluation produces it. A failed write interrupts the evaluation, and `finish` then gives the write's error. Evaluations can also read standard input, when the program didn't come from it. Ctrl-C interrupts the evaluation that is running. struct Printer { interrupt: Arc>, failure: Arc>>, input: Option> } diff --git a/cli/src/editor.rs b/cli/src/editor.rs index 139cb9e3..3da733a8 100644 --- a/cli/src/editor.rs +++ b/cli/src/editor.rs @@ -55,7 +55,7 @@ impl Input { let enter = key == KeyEvent::from('\r') || key == KeyEvent::from('\n'); let tab = key == KeyEvent::from('\t'); let plain = key.1.is_empty(); - let delimiter = plain && matches!(key.0, KeyCode::Char(c) if !c.is_alphabetic()); + let delimiter = plain && matches!(key.0, KeyCode::Char(c) if !c.is_ascii_alphabetic()); if tab || self.active && (enter || delimiter) { if let Some((start, prefix)) = entry(line, pos, self.aliases) { let found = matches(prefix); diff --git a/cli/src/worker.rs b/cli/src/worker.rs index 0e5eb10f..0fbeb571 100644 --- a/cli/src/worker.rs +++ b/cli/src/worker.rs @@ -25,7 +25,8 @@ pub(crate) fn run(output: &mut impl Write) -> io::Result<()> { let session = Session::new(); for (request, interrupt) in receive { let id = request.as_ref().ok().and_then(|r| r["id"].as_u64()); - let result = request.and_then(|r| reply(&session, &r, interrupt, output)).unwrap_or_else(|message| Reply::Json(basedpl::protocol::request_error(&message))); + let result = + request.and_then(|r| reply(&session, &r, interrupt, output)).unwrap_or_else(|message| Reply::Json(basedpl::protocol::request_error(&message))); if let Some(id) = id { active.lock().unwrap().remove(&id); } write_reply(output, id, result)?; } @@ -68,9 +69,9 @@ fn reply(session: &Session, request: &Value, interrupt: InterruptHandle, output: if let Some(spec) = request.get("timing") { return basedpl_testkit::timing::request(spec, &options.interrupt).map(Reply::Json); } if let Some(spec) = request.get("steps") { return basedpl_testkit::timing::steps_request(spec, &options.interrupt).map(Reply::Json); } if let Some(spec) = request.get("profile") { - return basedpl_testkit::timing::profile_request(spec, &options.interrupt, || - write_reply(output, request["id"].as_u64(), Reply::Json(serde_json::json!({"ready":true,"error":null}))) - .map_err(|error| error.to_string())).map(Reply::Json); + return basedpl_testkit::timing::profile_request(spec, &options.interrupt, || { + write_reply(output, request["id"].as_u64(), Reply::Json(serde_json::json!({"ready":true,"error":null}))).map_err(|error| error.to_string()) + }).map(Reply::Json); } if let Some(spec) = request.get("costs") { return basedpl_testkit::timing::file_request(spec, &options.interrupt).map(Reply::Json); } if let Some(spec) = request.get("check") { return basedpl_testkit::reference::runner::check_file(spec, options.timeout).map(Reply::Json); } diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index 27fd3610..5cd4ddc2 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -2,7 +2,7 @@ use std::{io::Write, process::{Command, Stdio}}; #[test] fn native_expression_and_diagnostic() { - for (code, expected) in [("2×3+4", "14\n"), ("⊂4ₓ ⋄ ⊂⊂4ₓ ⋄ ⊂1 2", "⊂4ₓ\n⊂⊂4ₓ\n⊂[1 2]\n"), ("f←{⍵=0?0;1+∇⍵-1} ⋄ f 500", "500\n")] + for (code, expected) in [("2×3+4", "14\n"), ("⊂4ₓ ◇ ⊂⊂4ₓ ◇ ⊂1 2", "⊂4ₓ\n⊂⊂4ₓ\n⊂[1 2]\n"), ("f←{ω=0?0;1+∇ω-1} ◇ f 500", "500\n")] { let output = Command::new(env!("CARGO_BIN_EXE_bpl")).args(["-e", code]).output().unwrap(); assert!(output.status.success()); @@ -47,13 +47,13 @@ fn worker_flushes_before_eof_and_recovers() { }); let code = |source: &str| json!({"code": source}).to_string(); for (request, expected, error_kind, printed) in [ - (code("v←⍳10"), Some(json!({"shape":[10], "data":(0..10).map(f64::from).collect::>(), "prototype":0.0})), None, vec![]), + (code("v←ι10"), Some(json!({"shape":[10], "data":(0..10).map(f64::from).collect::>(), "prototype":0.0})), None, vec![]), (json!({"id":2, "code":"+/v"}).to_string(), Some(json!(45.0)), None, vec!["45"]), ("{".into(), None, Some("REQUEST"), vec![]), (json!(3).to_string(), None, Some("REQUEST"), vec![]), (json!("1+2").to_string(), None, Some("REQUEST"), vec![]), - (json!({"id":"x", "code":"1+2"}).to_string(), None, Some("REQUEST"), vec![]), (code("⎕←7 ⋄ 1÷'a'"), None, Some("DOMAIN"), vec!["7"]), + (json!({"id":"x", "code":"1+2"}).to_string(), None, Some("REQUEST"), vec![]), (code("⎕←7 ◇ 1÷'a'"), None, Some("DOMAIN"), vec!["7"]), (code("(2+"), None, Some("SYNTAX"), vec![]), (code("⍝ \"quoted\"\n+/v"), Some(json!(45.0)), None, vec!["45"]), (code("f←+"), None, None, vec![]), - (code("fs←[+ ×]"), None, None, vec![]), (code("+/"), None, None, vec!["+/"]), (code("f←1⊃fs ⋄ 2 f 3"), Some(json!(6.0)), None, vec!["6"]), - (code(""), None, None, vec![]), (code("⍳0"), Some(json!({"shape":[0], "data":[], "prototype":0.0})), None, vec!["⍬"]), + (code("fs←[+ ×]"), None, None, vec![]), (code("+/"), None, None, vec!["+/"]), (code("f←1⊃fs ◇ 2 f 3"), Some(json!(6.0)), None, vec!["6"]), + (code(""), None, None, vec![]), (code("ι0"), Some(json!({"shape":[0], "data":[], "prototype":0.0})), None, vec!["⍬"]), ] { writeln!(input, "{request}").unwrap(); input.flush().unwrap(); @@ -83,7 +83,7 @@ fn worker_flushes_before_eof_and_recovers() { fn batch_stdin_and_persistent_repl() { for args in [vec!["-"], vec![]] { let mut child = Command::new(env!("CARGO_BIN_EXE_bpl")).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap(); - child.stdin.take().unwrap().write_all("v←⍳10\n+/v\n".as_bytes()).unwrap(); + child.stdin.take().unwrap().write_all("v←ι10\n+/v\n".as_bytes()).unwrap(); let result = child.wait_with_output().unwrap(); assert!(result.status.success()); assert_eq!(String::from_utf8(result.stdout).unwrap(), "45\n"); @@ -99,8 +99,8 @@ fn programs_read_standard_input() { let output = child.wait_with_output().unwrap(); (String::from_utf8(output.stdout).unwrap(), String::from_utf8(output.stderr).unwrap()) }; - assert_eq!(run(&["-e", "⎕ ⋄ ⍴⎕ ⋄ ⎕ ⋄ ⍬≡⎕"], "first\n\nlast"), ("\"first\"\n[0]ₓ\n\"last\"\n$t\n".into(), String::new())); - assert_eq!(run(&["-e", "⎕ ⋄ ≢•nget \"-\""], "head\nab\ncd\n"), ("\"head\"\n6ₓ\n".into(), String::new())); + assert_eq!(run(&["-e", "⎕ ◇ ρ⎕ ◇ ⎕ ◇ ⍬≡⎕"], "first\n\nlast"), ("\"first\"\n[0]ₓ\n\"last\"\n$t\n".into(), String::new())); + assert_eq!(run(&["-e", "⎕ ◇ ≢•nget \"-\""], "head\nab\ncd\n"), ("\"head\"\n6ₓ\n".into(), String::new())); // Standard input holds the program itself here. for args in [&["-"][..], &[]] { assert!(run(args, "⎕\n").1.contains("IO ERROR: standard input is unavailable here")); } } diff --git a/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json b/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json index ec30e69e..c8007243 100644 --- a/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json +++ b/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json @@ -45,7 +45,7 @@ "name": "string.quoted.single.bpl" }, { - "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←⎕•⍺⍵⍶⍹∇⍢⍝⋄⁝\\?¯∞⍬\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿¨ᵇ⍨↣⍤∘⍠⍥↢\\.⊗⌸⍣⇄⊘@∂⍚⌺/⌿\\\\⍀⁻⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚₐᵣ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+", + "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←⎕•αω⍶⍹∇⍢⍝◇⁝\\?¯∞⍬\\+\\-×÷⌈⌊\\|\\*⊛○π√!∧∨⊼⊽\\~=≠<≤>≥ι↦:ρ≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿¨ᵇ⍨↣⍤∘⍠⍥↢\\.⊗⌸⍣⇄⊘@∂⍚⌺/⌿\\\\⍀⁻⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚₐᵣ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+", "name": "variable.other.bpl" }, { @@ -86,7 +86,7 @@ } }, { - "match": "[\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿]", + "match": "[\\+\\-×÷⌈⌊\\|\\*⊛○π√!∧∨⊼⊽\\~=≠<≤>≥ι↦:ρ≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿]", "name": "support.function.bpl" }, { @@ -102,11 +102,11 @@ "name": "keyword.operator.dyadic.bpl" }, { - "match": "[⍺⍵⍶⍹∇⍢]", + "match": "[αω⍶⍹∇⍢]", "name": "variable.parameter.bpl" }, { - "match": "[←⎕⋄⁝\\?₀₁₂₃₄₅₆₇₈₉₋]", + "match": "[←⎕◇⁝\\?₀₁₂₃₄₅₆₇₈₉₋]", "name": "keyword.control.bpl" } ] diff --git a/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json b/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json index 1c762385..cf1fd6bf 100644 --- a/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json +++ b/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json @@ -19,7 +19,7 @@ "applyEndPatternLast": true }, { - "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←是⎕出入•系⍺甲⍵乙⍶丙⍹丁∇递遞⍢循⍝注⋄句⁝隔\\?若¯负負∞永⍬空\\+加\\-减減×乘号號÷除⌈上⌊下\\|绝余絕餘\\*幂冪⍟对對○圆圓π周√根!阶拣階揀∧极且極∨析或⊼方否⊽双莫雙\\~非无無=等≠别初別<小≤限退>大≥达进達進⍳位↦至:键卸鍵⍴形≢数异數異≡同深,连連⍪接矩⊂包⊃选合選⊆巢段∊属展屬∪并独併獨∩交重⍋升⍒降↑取↓去拆⌽转轉⊖纵縱⍉翻⊤编編⊥译譯⍎行⍕文⌷索⌹解逆\\#复複↕窗⍭素⨸因⌻零值⍸处间處間⍷寻群尋⊢右⊣左¿掷抽擲¨遍ᵇ按⍨己换換↣先⍤秩∘承⍠轴軸⍥经經↢后後\\.内內⊗表⌸组組⍣次止⇄优優⊘兼@于於∂导梯導⍚择擇⌺邻鄰/总總⌿汇匯\\\\累⍀续續⁻反⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚₐᵣ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+", + "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←是⎕出入•系α甲ω乙⍶丙⍹丁∇递遞⍢循⍝注◇句⁝隔\\?若¯负負∞永⍬空\\+加\\-减減×乘号號÷除⌈上⌊下\\|绝余絕餘\\*幂冪⊛对對○圆圓π周√根!阶拣階揀∧极且極∨析或⊼方否⊽双莫雙\\~非无無=等≠别初別<小≤限退>大≥达进達進ι位↦至:键卸鍵ρ形≢数异數異≡同深,连連⍪接矩⊂包⊃选合選⊆巢段∊属展屬∪并独併獨∩交重⍋升⍒降↑取↓去拆⌽转轉⊖纵縱⍉翻⊤编編⊥译譯⍎行⍕文⌷索⌹解逆\\#复複↕窗⍭素⨸因⌻零值⍸处间處間⍷寻群尋⊢右⊣左¿掷抽擲¨遍ᵇ按⍨己换換↣先⍤秩∘承⍠轴軸⍥经經↢后後\\.内內⊗表⌸组組⍣次止⇄优優⊘兼@于於∂导梯導⍚择擇⌺邻鄰/总總⌿汇匯\\\\累⍀续續⁻反⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚₐᵣ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+", "name": "variable.other.bpl" }, { diff --git a/editors/jetbrains/BPL.xml b/editors/jetbrains/BPL.xml index 21b6b5d9..9416ebb6 100644 --- a/editors/jetbrains/BPL.xml +++ b/editors/jetbrains/BPL.xml @@ -14,7 +14,7 @@